{"id":37759415,"url":"https://github.com/ciokan/guardini","last_synced_at":"2026-01-16T14:34:43.188Z","repository":{"id":57256551,"uuid":"80474934","full_name":"ciokan/guardini","owner":"ciokan","description":"Guardini is a package designed for programmers who are looking to sell access to their API endpoints and need a good, battle tested rate-limiter","archived":false,"fork":false,"pushed_at":"2017-02-01T01:03:33.000Z","size":16,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-10T04:26:58.108Z","etag":null,"topics":["api-wrapper","attacker","bruteforce","middleware","rate-limit"],"latest_commit_sha":null,"homepage":"http://www.guardini.io","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ciokan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-30T23:38:06.000Z","updated_at":"2022-12-11T02:34:35.000Z","dependencies_parsed_at":"2022-08-25T02:30:36.214Z","dependency_job_id":null,"html_url":"https://github.com/ciokan/guardini","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ciokan/guardini","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciokan%2Fguardini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciokan%2Fguardini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciokan%2Fguardini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciokan%2Fguardini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ciokan","download_url":"https://codeload.github.com/ciokan/guardini/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciokan%2Fguardini/sbom","scorecard":{"id":282998,"data":{"date":"2025-08-11","repo":{"name":"github.com/ciokan/guardini","commit":"ac70c3187af32abf12d0e96fda959f390f49740e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":"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":"Code-Review","score":0,"reason":"Found 0/6 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":"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":"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":"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":"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":"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"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":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"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"}}]},"last_synced_at":"2025-08-17T16:25:10.813Z","repository_id":57256551,"created_at":"2025-08-17T16:25:10.813Z","updated_at":"2025-08-17T16:25:10.813Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28479396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: 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":["api-wrapper","attacker","bruteforce","middleware","rate-limit"],"created_at":"2026-01-16T14:34:42.757Z","updated_at":"2026-01-16T14:34:43.181Z","avatar_url":"https://github.com/ciokan.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CircleCI](https://circleci.com/gh/ciokan/guardini/tree/master.svg?style=svg)](https://circleci.com/gh/ciokan/guardini/tree/master)\n\n## Introduction\n\nGuardini is used in production at [infoip.io](https://infoip.io) and it has proven to be a reliable rate limiter so far.\nIt has some added functionality that makes it applicable to projects or websites that want to sell access to their APIs.\n\nGuardini makes use of plans (payment plans if you wish) and is able to rate limit according to their specs allowing you\nto sell different kinds of access to your API (have a look at the infoip [pricing page](http://www.infoip.io/pricing.html)).\n\n## Dependencies\n\nGuardini heavily depends on a Redis backend (this is the only requirement actually) and is tightly integrated with it. The\npackage injects a `lua` script into Redis in order to perform it's queries super fast without too much client-server communication\nas opposed to other packages.\n\n## Usage\n\nThe package is easy to setup and requires a minimum configuration to provide it's functionality. It tries to stay unopinionated\nas much as possible without any requirements in regards to the framework you use. It is super easy to integrate as a middleware\nin almost any nodejs web framework out there since it's only goal is to callback with a denied `true` or `false` parameter.\n\n### Instance parameters\n\nGuardini requires the following parameters:\n\n- `redisClient connection`: A redis client connection\n- `options`: Guardini options (see next section for details)\n- `planProvider`: This is a method which Guardini will call whenever it receives a request to check a `token` that is not in it's\ncache/database already. The package could be dumber than this and require that you pass a plan along with a token at each request\nbut this will greatly slow down your response time if you receive a lot of requests. To be as efficient as possible Guardini requests\nthe plan of a token only when it's not present in it's cache. So, this plan provider takes 2 parameters:\n    - `token`: The token for which the provider should respond with a plan name\n    - `callback`: A callback function which is used to pass back the plan\n\n### Instance options\n\n- `namespace`: optional namespace if you have multiple applications/endpoints with different plans and users are given the same token\n- `plans`: object containing the plans (separation) with their limits. The plan name is represented by the keys you enter here\n  - `limits`: Each plan should have a `limits` key with an array of of arrays with 2 values: the time interval (in seconds) for which\n  the second value (the limit of requests in that interval) is allowed. The bigger the interval the longer you will have keys inside\n  redis so it's advisable to keep the intervals small.\n- `cacheInvalidateTtl`: Guardini is caching some results inside redis to avoid requesting the plan for each token at every request\nin order to save time and resources. The length (in seconds) of this cache time should be added here. Default is `3600` seconds (one hour).\n\n## An example is worth 1000 words\n\nThis example contains the plans that we use at infoip.io but everything has been simplified and made static so that you can understand\nhow it's supposed to work. You can use a mongo/mysql/etc database to query for the plans and then setup Guardini's options. The plan\nprovider should also make use of a database to lookup that `token` and see what plan it has.\n\n#### Setup:\n```javascript\nconst guard = new Guardini(redisClient, {\n\tnamespace: 'infoip',\n\tplans: {\n\t\tfree: {\n\t\t\t//\tallows for 1 request per second and 1000/day for free users\n\t\t\tlimits: [[1, 1], [86400, 1000]]\n\t\t},\n\t\tbasic: {\n\t\t\t//\tallows for 2 requests per second and 2000/day\n\t\t\tlimits: [[1, 2], [86400, 2000]]\n\t\t},\n\t\tpro: {\n\t\t\t//\tallows for 5 requests per second, 20000/day, 2,000,000/month\n\t\t\t//  this one will persist the keys for a longer period because\n\t\t\t//  of the 30 day period specified\n\t\t\tlimits: [[1, 5], [86400, 20000], [86400 * 30, 2000000]]\n\t\t},\n\t\tmega: {\n\t\t\t//\tallows for 20 requests per second and 80000/day\n\t\t\tlimits: [[1, 20], [86400, 80000]]\n\t\t},\n\t\tultra: {\n\t\t\t//\tallows for 500 requests per second and 500000/day\n\t\t\tlimits: [[1, 500], [86400, 500000]]\n\t\t}\n\t}\n}, function planProvider(token, callback) {//\tplan provider\n\t// this method should provide the plan of a token when requested\n\t// I highly encourage you to perform a database query here to retrieve\n\t// the plan of the provided token. This example is static in order to\n\t// better outline how the config should be done\n\tswitch(token){\n\t\tcase 'token-1234':\n\t\t\tcallback(null, 'basic');\n\t\t\tbreak;\n\t\tcase 'token-abcd':\n\t\t\tcallback(null, 'ultra');\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcallback(null, null);\n\t}\n});\n```\n\n#### Call to check (expressjs example)\n\n```javascript\nrouter.get('/endpoint', (req, res) =\u003e {\n\tconst clientIp = getClientIp(req);\n\n\t// token can be null in which case Guardini will check if there's\n\t// a free plan available and rate-limit based on the client ip\n\tconst token = getToken(req);\n\n\tguardini.check(token, clientIp, (err, denied) =\u003e {\n\t\tif (err) {\n\t\t\tres.sendStatus(503);\n\t\t} else {\n\t\t\tif (denied) {\n\t\t\t\tres.sendStatus(429);\n\t\t\t} else {\n\t\t\t\t// client is allowed - respond here\n\t\t\t}\n\t\t}\n\t});\n});\n```\n\n## Providing a free plan\n\nIt's crucial to be able to provide a free plan to any API and Guardini makes use of it. Simply specifying `free` as the plan key (as in the example above)\nwill enable a `freemium` type of rate-limit. If a client comes in without a token (or with invalid token) Guardini will look for this plan and, if available,\nit will rate limit the requests based on the user ip address. If there is no `free` plan inside the `plans` definition then guardini will immediately\nrespond with a deny.\n\nIf you provide a free API but simply want to rate limit you can simply specify a `free` plan and Guardini will rate limit only based on ip address.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fciokan%2Fguardini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fciokan%2Fguardini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fciokan%2Fguardini/lists"}