{"id":15601166,"url":"https://github.com/hacknlove/cuchito","last_synced_at":"2026-05-16T15:02:04.801Z","repository":{"id":39452855,"uuid":"271586746","full_name":"hacknlove/cuchito","owner":"hacknlove","description":"proxy that multiplies the requests, record the sesion, and replays it","archived":false,"fork":false,"pushed_at":"2023-01-07T19:16:42.000Z","size":2324,"stargazers_count":2,"open_issues_count":19,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-19T15:07:39.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hacknlove.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-11T15:50:03.000Z","updated_at":"2024-04-25T02:54:06.000Z","dependencies_parsed_at":"2023-02-07T22:31:22.840Z","dependency_job_id":null,"html_url":"https://github.com/hacknlove/cuchito","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hacknlove/cuchito","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacknlove%2Fcuchito","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacknlove%2Fcuchito/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacknlove%2Fcuchito/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacknlove%2Fcuchito/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hacknlove","download_url":"https://codeload.github.com/hacknlove/cuchito/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacknlove%2Fcuchito/sbom","scorecard":{"id":452289,"data":{"date":"2025-08-11","repo":{"name":"github.com/hacknlove/cuchito","commit":"8949a65d73a8a93f69cccfe067f9bbf4d28416a7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/11 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"47 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-w7rc-rwvf-8q5r","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T08:30:59.491Z","repository_id":39452855,"created_at":"2025-08-19T08:30:59.492Z","updated_at":"2025-08-19T08:30:59.492Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33107564,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T04:41:52.686Z","status":"ssl_error","status_checked_at":"2026-05-16T04:41:52.009Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T02:19:05.028Z","updated_at":"2026-05-16T15:02:04.783Z","avatar_url":"https://github.com/hacknlove.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cuchito\n**TitM *(test in the middle)* - made easy**\n\n![cuchito-logo](assets/logo.svg)\n\n## WARNING\nThis software is under intense development, this documentation it's not stable at all. Everithing could change.\n\nDo not use in production. Use it to try out the concept, to give feedback and to help with the development.\n\n## What is cuchito?\n\nCuchito is a **test in the middle** tool that helps developers, QAs and devops, in their daily tasks with a complete set of helpful features:\n\n* **Manual stress test:** Connect cuchito between the front and the back, and make it multiply your API calls as many times as you need.\n* **Automated stress test:** Save manual stress sessions so they can run later as unatended automated stress tests.\n* **API schema validation:** Use [@hapi/joi](https://github.com/hapijs/joi) schema validations in the requests or responses to catch bugs\n* **API test:** write tests easier than ever, and run then manually or automatically.\n* **API mocking:** Replace or mutate request and responses, to try new features or edge cases for both the front and the back.\n* **re-routing:** Re-route the paths, so you can merge different servers, environment, versions and endpoints in one API.\n* **Real time config reload:** Change your session configuration on the fly.\n\n## Quick guides\n\n### First steps\n\n#### Initialize a cuchito project\n```\nnpm init cuchito foo\ncd foo\n```\n\n#### Edit the file named `cuchito.js` to:\n\n* set the field `host` to point to the API you want to stress.\n* set the IP and port you want your cuchito server to listen to.\n\n#### Start a a manual session\n\n```\nnpm start\n```\n\nManual sessions proxies the API.\n\nEventhough the requests and responses can be multiplied, mutated, validated, tested, logged, and re-routed, cuchito works transparentely so any client should will as expected.\n\nYou just need to connect the client to the ip and port that you has been set in `./cuchito.js`.\n\n#### Run a saved session\n\n```\nnpm test\n```\n\nReplay a sessions to send the saved requests to the host, in mostly the same fashion (multiplying, mutating, validating, testing, loging, and/or rerouting).\n\nThere are two differences:\n\n* Requests are not validated nor tested, only responses.\n* If any response validation or test fails, the reproduction stops with an error.\n\n### Basic manual Stress Test\n\nEdit the file `./cuchito.js`\n\n* config the request multiplication:\n  * add the methods don't you want to multiply to the `skipMethod` object, as truthy.\n  * `count` sets the amount of times each incoming request will be sent to the host\n  * `interval` sets the waiting milliseconds between those extra request\n\n```\n...\n  multiply: {\n    count: 50,\n    interval: 500,\n    skipMethods: {\n      'POST': 1,\n      'PUT': 1,\n      'DELETE': 1\n    },\n  },\n...\n```\n\nThis example configures cuchito to resend any request 50 times, in intervals of 500 milliseconds, skipping those whose method are POST, PUT or DELETE.\n\n\n### Basic automated stress tests\n\nUnless you have changed the record configuration, every manual stress test you have done has been recorded.\n\nThese recorded sessions can be replayed with the following command:\n\n```\nnpm test\n```\n\nnew saved session does not replace or overwrite the current save session, but merge into it.\n\nPlease be aware that all recorded sessions would be reproduced simultaneously.\n\n\n### Basic forensics\n\ncuchito dumps log files with the whole request and response, for each not cloned requests.\n\nThis logs are stored in both modes, manual an automated.\n\nThe file name is a csv row, with the next: timestamp, method, path, status, test result. For instance: `./logs/1592566705414,GET,⁄foo⁄bar?baz=qux,200,ok.yml`\n\nThe file is a `yml` with the fields `request`, `response`, and optionally `originalRequest`, `originalResponse`, `error` and `logs`\n\n```\nrequest:\n  path: /foo/bar?baz=qux\n  params:\n    fooId: bar\n  query:\n    baz: qux\n  headers:\n    content-type: application/json\n  method: GET\n  body: {}\nresponse:\n  headers:\n    content-type: application/json; charset=utf-8\n  status: 200\n  body:\n    result: ok\n    code: 200\n```\n\nWe will learn about the optional fields in the sections about mutations, advanced tests, and advanced logs.\n\n### Request schema validation\n\nWhen you are running a manual session (and only in manual sessions), you can validate the incoming requests.\n\nIn the folder `./endpoints` create a `test.js` file in a path that mimics the endpoint's route and method you want to test. For instance `./endpoints/foo/bar/POST/test.js`\n\nYou can also set up dinamic urls, wrapping the parameter name with brackets, like `./endpoints/foo/[fooId]/PUT/test.js`\n\nTo validate the schema of the request this file needs to export a [@hapi/joi object](https://github.com/hapijs/joi#readme) as `requestSchema`\n\n```\nconst joi = require('@hapi/joi');\n\nexports.requestSchema = joi.object({\n  headers: joi.object(\n    'content-type': joi.valid('application/json'),\n  ),\n  body: ...\n  params: ...,\n  query: ...,\n})\n```\n\nIf the request's body is json and the content type is application/json, `body` will be a javascript object. Otherwise it will be a string.\n\n`params` is an object with the route params. So if the file is `./endpoints/foo/[fooId]/PUT/test.js` and the request is `PUT /foo/bar` params will be `{ foodId: 'bar' }`\n\n`query` is an object with the query params So if thethe request is `PUT /foo/bar?baz=qux` query will be `{ baz: 'qux' }`\n\nNo coercion is done, the values in `params` and `query` are always strings.\n\nThe validation errors will show up red in the console. The session will not stop, and the request is not skiped. It will reach the host.\n\nOn top of that, the logs and recordings of the requests whose validation has failed will have the extension `.error.yml` instead of `.ok.yml`\n\n### Response schema validation\n\nThe response schema validation works much like the request schema validation.\n\nThe differences are:\n\n* The `@hapi/joi` validations must be exported as `responseSchema`, and obviously the same `.../test.js` can export request validations and respons evalidations.\n\n* The response schema validation runs on both modes, manual and automated.\n\n* Automated sessions stop when any response schema validation fails.\n\n\n```\nconst joi = require('@hapi/joi');\n\nexports.responseSchema = joi.object({\n  status: joi.valid(200),\n  headers: ...,\n  body: ...,\n})\n```\nIf the body is json and the content type is application/json, `body` will be a javascript object. Otherwise it will be a string.\n\n\n## advanced guides\n\n### configuration\n\nThe main config file is `./cuchito.js`\n\nThis javascript is executed, in can includes any logic you need to create dinamic configurations.\n\nJust export a javascript object with all the configuration you need.\n\n* `host`: Host to reverse-proxy the request to\n* `ip`: Ip to listen to\n* `port`: port to listen to\n* `maxTimeSpan`: Send an error to the client when the request takes more than `maxTimeSpan` milliseconds. 0 or falsy to disable the feature and wait forever.\n\nIf your configuration depends on some asynchronous source, or it could changes on run time you have to include a `oChange` function that accept a handler, that you can call with the actualized configuration as many times as you want.\n\nYou can disable the logging by removing the field `logs` in the file `cuchito.js` or by renaming it to `logs_` for instance.\n\nYou can configure the logs independently for each endpoint or endpoint groups, by creating a `conf.js` in the `endpoints` folder in a path that mimics the endpoints' route you want to configure.\n\nThe fields of the `./endpoints/foo/[fooId]/GET/conf.js` overwrite the fields of the `cuchito.js` file.\n\n#### skip-all log-some strategy.\n\nYou can disable all the logs in `./cuchito.js` and enable it for some endpoint in `./endpoints/some/endpoint/GET/conf.js`\n\n#### log-all skip-some strategy\n\nYou can disable all the logs in `./cuchito.js` and enable it for some endpoint in `./endpoints/some/endpoint/GET/conf.js`\n\n\n### Mutate requests\n\n### Mutate responses\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacknlove%2Fcuchito","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhacknlove%2Fcuchito","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacknlove%2Fcuchito/lists"}