{"id":19387682,"url":"https://github.com/haraka/haraka-results","last_synced_at":"2026-04-02T12:05:22.333Z","repository":{"id":16520107,"uuid":"80097178","full_name":"haraka/haraka-results","owner":"haraka","description":"Results storage for Haraka connections \u0026 transactions","archived":false,"fork":false,"pushed_at":"2026-03-24T19:45:18.000Z","size":63,"stargazers_count":3,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-26T00:58:32.903Z","etag":null,"topics":["haraka","json","pub-sub","redis-client","results"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/haraka-results","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/haraka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-01-26T08:24:45.000Z","updated_at":"2026-03-24T19:45:05.000Z","dependencies_parsed_at":"2024-06-18T20:06:39.398Z","dependency_job_id":"9736ed86-8884-416d-bda4-7103423a9127","html_url":"https://github.com/haraka/haraka-results","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/haraka/haraka-results","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraka%2Fharaka-results","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraka%2Fharaka-results/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraka%2Fharaka-results/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraka%2Fharaka-results/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haraka","download_url":"https://codeload.github.com/haraka/haraka-results/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraka%2Fharaka-results/sbom","scorecard":{"id":455525,"data":{"date":"2025-08-11","repo":{"name":"github.com/haraka/haraka-results","commit":"273510efd2db429693999a4f7a3b9e7aaeaa82ea"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Code-Review","score":0,"reason":"Found 2/29 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":"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":"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":"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":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/codeql.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/codeql.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/haraka/haraka-results/publish.yml/master?enable=pin","Info:   0 out of   6 third-party GitHubAction dependencies pinned"],"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":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Warn: no topLevel permission defined: .github/workflows/codeql.yml:1","Warn: no topLevel permission defined: .github/workflows/publish.yml:1","Info: no jobLevel write permissions found"],"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":"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":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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 26 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"}}]},"last_synced_at":"2025-08-19T09:29:58.544Z","repository_id":16520107,"created_at":"2025-08-19T09:29:58.544Z","updated_at":"2025-08-19T09:29:58.544Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305973,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"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":["haraka","json","pub-sub","redis-client","results"],"created_at":"2024-11-10T10:10:10.020Z","updated_at":"2026-04-02T12:05:22.325Z","avatar_url":"https://github.com/haraka.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# haraka-results\n\n[![Build Status][ci-img]][ci-url]\n[![Code Coverage][cov-img]][cov-url]\n[![Code Climate][clim-img]][clim-url]\n\nAdd, log, retrieve, and share the results of plugin tests.\n\n## Synopsis\n\nResults is a structured way of storing results from plugins across a\nsession, allowing those results to be retrieved later or by other plugins.\n\nResults objects are present on every Haraka connection _and_ transaction. When\nin a SMTP transaction, results from _both_ are applicable to that transaction.\n\n## Usage\n\nUse results in your plugins like so:\n\n```js\nexports.my_first_hook = function (next, connection) {\n\n    // run a test\n    ......\n\n    // store the results\n    connection.results.add(this, {pass: 'my great test' })\n\n    // run another test\n    .....\n\n    // store the results\n    connection.results.add(this, {fail: 'gotcha!', msg: 'show this'})\n}\n```\n\nStore the results in the transaction (vs connection):\n\n```js\n   connection.transaction.results.add(this, {...});`\n```\n\n### Config options\n\nEach plugin can have custom settings in results.ini to control results logging.\nThere are three options available: hide, order, and debug.\n\n- hide - a comma separated list of results to hide from the output\n- order - a comman separated list, specifing the order of items in the output\n- debug - log debug messages every time results are called\n\n```ini\n; put this in config/results.ini\n[plugin_name]\nhide=skip\norder=msg,pass,fail\ndebug=0\n```\n\n### Results Functions\n\n#### add\n\nStore information. Most calls to `results` will append data to the lists\nin the connection. The following lists are available:\n\n    pass  - names of tests that passed\n    fail  - names of tests that failed\n    skip  - names of tests that were skipped (with a why, if you wish)\n    err   - error messages encountered during processing\n    msg   - arbitratry messages\n\n    human - a custom summary to return (bypass collate)\n    emit  - log an INFO summary\n\nWhen err results are received, a logerror is automatically emitted, saving the\nneed to specify {emit: true} with the request.\n\nExamples:\n\n```js\n    const results = connection.results\n    results.add(this, {pass: 'null_sender'})\n    results.add(this, {fail: 'single_recipient'})\n    results.add(this, {skip: 'valid_bounce'}\n    results.add(this, {err: 'timed out looking in couch cushions'})\n    results.add(this, {msg: 'I found a nickel!', emit: true})\n```\n\nIn addition to appending values to the predefined lists, arbitrary results\ncan be stored in the cache:\n\n```js\nresults.add(this, { my_result: 'anything I want' })\n```\n\nWhen arbirary values are stored, they are listed first in the log output. Their\ndisplay can be suppressed with the **hide** option in results.ini.\n\n#### incr\n\nIncrement counters. The argument to incr is an object with counter names and\nincrement values. Examples:\n\n```js\nresults.incr(this, { unrecognized_commands: 1 })\n\nresults.incr(this, { karma: -1 })\nresults.incr(this, { karma: 2 })\n```\n\n#### push\n\nAppend items onto arrays. The argument to push is an object with array names and\nthe new value to be appended to the array. Examples:\n\n```js\nresults.push(this, { dns_recs: 'name1' })\nresults.push(this, { dns_recs: 'name2' })\n```\n\n#### collate\n\n```js\nconst summary = results.collate(this)\n```\n\nFormats the contents of the result cache and returns them. This function is\ncalled internally by `add()` after each update.\n\n#### get\n\nRetrieve the stored results as an object. The only argument is the name of the\nplugin whose results are desired.\n\n```js\n    const geoip = results.get('geoip')\n    if (geoip \u0026\u0026 geoip.distance \u0026\u0026 geoip.distance \u003e 2000) {\n        ....\n    }\n```\n\nKeep in mind that plugins also store results in the transaction. Example:\n\n```js\n    const sa = connection.transaction.results.get('spamassassin')\n    if (sa \u0026\u0026 sa.score \u003e 5) {\n        ....\n    }\n```\n\n#### has\n\nCheck result contents for string or pattern matches.\n\nSyntax:\n\n```js\nresults.has('plugin_name', 'result_name', 'search_term')\n```\n\n- result_name: the name of an array or string in the result object\n- search_term: a string or RegExp object\n\n### More Examples\n\n#### Store Results:\n\n```js\nresults.add(this, { pass: 'some_test' })\nresults.add(this, { pass: 'some_test(with reason)' })\n```\n\n#### Retrieve exact match with **get**:\n\n```js\nif (results.get('plugin_name').pass.indexOf('some_test') !== -1) {\n  // some_test passed (1x)\n}\n```\n\n#### Retrieve a string match with **has**\n\n```js\nif (results.has('plugin_name', 'pass', 'some_test')) {\n  // some_test passed (1x)\n}\n```\n\nThe syntax for using **has** is a little more pleasant.\n\nBoth options require one to check for each reason which is unpleasant when\nand all we really want to know is if some_test passed or not.\n\n#### Retrieve a matching pattern:\n\n```js\nif (results.has('plugin_name', 'pass', /^some_test/)) {\n  // some_test passed (2x)\n}\n```\n\n### Private Results\n\nTo store structured data in results that are hidden from the human and\nhuman_html output, prefix the name of the key with an underscore.\n\nExample:\n\n```js\nresults.add(this, { _hidden: 'some data' })\n```\n\n## Redis Pub/Sub\n\nIf a redis client is found on server.notes.redis, then new results are JSON\nencoded and published to Redis on the channel named `result-${UUID}`. This\nfeature can be disabled by setting `[main]redis_publish=false` in results.ini.\nPlugins can recieve the events by psubscribing (pattern subscribe) to the\nchannel named `result-${UUID}*` where ${UUID} is the connection UUID.\n\nThis is from the karma plugin subscribing on the `connect_init` hook:\n\n```js\nexports.register = function (next, server) {\n  this.inherits('redis')\n\n  register_hook('connect_init', 'redis_subscribe')\n  register_hook('disconnect', 'redis_unsubscribe')\n}\n\nexports.redis_subscribe = function (next, connection) {\n  this.redis_subscribe(connection, function () {\n    connection.notes.redis.on('pmessage', (pattern, channel, message) =\u003e {\n      // do stuff with messages that look like this\n      // {\"plugin\":\"karma\",\"result\":{\"fail\":\"spamassassin.hits\"}}\n      // {\"plugin\":\"geoip\",\"result\":{\"country\":\"CN\"}}\n    })\n    next()\n  })\n}\nexports.redis_unsubscribe = function (next, connection) {\n  this.redis_unsubscribe(connection)\n}\n```\n\n[ci-img]: https://github.com/haraka/haraka-results/actions/workflows/ci.yml/badge.svg\n[ci-url]: https://github.com/haraka/haraka-results/actions/workflows/ci.yml\n[cov-img]: https://codecov.io/github/haraka/haraka-results/coverage.svg\n[cov-url]: https://codecov.io/github/haraka/haraka-results\n[clim-img]: https://qlty.sh/gh/haraka/projects/haraka-results/maintainability.svg\n[clim-url]: https://qlty.sh/gh/haraka/projects/haraka-results\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharaka%2Fharaka-results","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharaka%2Fharaka-results","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharaka%2Fharaka-results/lists"}