{"id":21304931,"url":"https://github.com/maximilianmairinger/sanitizeagainst","last_synced_at":"2026-02-13T14:09:54.324Z","repository":{"id":143586370,"uuid":"611351602","full_name":"maximilianMairinger/sanitizeAgainst","owner":"maximilianMairinger","description":"Highly customizable, but simple to use, sanitization of objects and primitives.","archived":false,"fork":false,"pushed_at":"2024-10-18T16:05:40.000Z","size":107,"stargazers_count":0,"open_issues_count":8,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-21T09:25:47.851Z","etag":null,"topics":["obj","object","pick","sani","sanitize","schema","validation","verify"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/sanitize-against","language":"TypeScript","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/maximilianMairinger.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-08T16:41:31.000Z","updated_at":"2024-10-18T16:05:44.000Z","dependencies_parsed_at":"2024-10-21T18:08:03.848Z","dependency_job_id":null,"html_url":"https://github.com/maximilianMairinger/sanitizeAgainst","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximilianMairinger%2FsanitizeAgainst","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximilianMairinger%2FsanitizeAgainst/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximilianMairinger%2FsanitizeAgainst/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximilianMairinger%2FsanitizeAgainst/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maximilianMairinger","download_url":"https://codeload.github.com/maximilianMairinger/sanitizeAgainst/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225759609,"owners_count":17519853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["obj","object","pick","sani","sanitize","schema","validation","verify"],"created_at":"2024-11-21T16:16:24.396Z","updated_at":"2026-02-13T14:09:54.291Z","avatar_url":"https://github.com/maximilianMairinger.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sanitize Against\r\n\r\nSanitize Against is a library for sanitizing objects, intended to filter the options of incoming requests on the server. It is highly customizable, but simple to use and tries to be decently fast while being safe.\r\n\r\n## Why?\r\n\r\n* If you want to filter out unwanted properties from objects. Thus preventing [`__proto__` poisoning](https://medium.com/intrinsic-blog/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96). \r\n\r\n* If you want a simple way to implement default properties or set the others as required to enforce a certain structure.\r\n\r\n* If you want to sanitize your object's properties, to be of a certain type (e.g. number) and/or have a certain value range (e.g. 0-100).\r\n\r\n\r\n## Installation\r\n\r\n```shell\r\n $ npm i sanitize-against\r\n```\r\n\r\n### ESM\r\n\r\n```ts\r\nimport sanitize, { OR, AND, NOT } from \"sanitize-against\"\r\n```\r\n\r\n### CJS\r\n\r\n```ts\r\nconst { sanitize, OR, AND, NOT } = require(\"sanitize-against\")\r\n```\r\n\r\n# Usage\r\n\r\nSimple example of how to integrate it into a web server. We will look at the specific patterns and outputs later.\r\n\r\n```ts\r\nconst userPattern = {\r\n  name: String,\r\n  \"age?\": Number,\r\n  pin: OR(Number, String),\r\n  settings: {\r\n    darkMode: false,\r\n    fontSize: 16\r\n  }\r\n}\r\n\r\nconst against = sanitize(userPattern)\r\n\r\n// example route\r\napp.post(\"/user\", (req, res) =\u003e {\r\n  try {\r\n    const user = against(JSON.parse(req.body))\r\n    // Do something with user\r\n  }\r\n  catche(e) {\r\n    // The error.msg is not terribly useful.\r\n    res.status(400).send(\"error\")\r\n  }\r\n})\r\n```\r\n\r\n## Patterns\r\n\r\nThe main constructor function `sanitize` takes a pattern as input. A pattern is not necessarily an object. The simplest pattern conceptually, and at the same time the most powerful one, would be a function. \r\n\r\n### Function\r\n\r\nThis function will be called (with the input as first argument) when a new input is supposed to be sanitized. The function should throw when the input is not valid and otherwise return the okay (sanitized) value.\r\n\r\n```ts\r\nconst pattern = (input: any) =\u003e {\r\n  if (input === 2) throw new Error(\"Input is 2\")\r\n  else return input\r\n}\r\n\r\nconst against = sanitize(pattern)\r\n\r\nagainst(1) // 1\r\nagainst(2) // throws\r\nagainst(\"litterally anything else\") // \"litterally anything else\"\r\n```\r\n\r\nWhile this is hardly an abstraction at all, it is the basis for all other patterns. The function can be used to implement any kind of validation. For example, you could use it to check if the input is a number and if it is in a certain range. To simplify the process of creating such constraints, there are predefined patterns, or rather values which are mapped to predefined functions. We will describe them in the next couple sections. But it is important to keep in mind the option to fall back to a function when the predefined patterns are not specific enough. In a later chapter we will introduce boolesch logic, which can be used to combine (predefined) patterns with custom ones.\r\n\r\n\u003e Note: I use pattern, constraint and custom functions interchangeably.\r\n\r\n\r\n\r\n### Primitives\r\n\r\nYou may use the values `Number`, `Boolean` and `String` (the primitive value constructors in the global scope) to specify that the input should be of that type. If the input is not of that type, or not set at all, it will throw. There is intentionally no attempt made at converting e.g. a string to a number. This is to prevent unexpected behavior and tbh I don't see a use case for it, as the frontend should be capable of converting it, and correctly sending it as a number. If you want to allow strings and numbers, you can use the `OR` combinator [see below](#or-combinator). If you disagree, please open an issue and explain your use case, and in the meantime simply fall back to a [custom function](#function). \r\n\r\n```ts\r\nconst pattern = Number\r\nconst against = sanitize(pattern)\r\n\r\nagainst(1) // 1\r\nagainst(123) // 123\r\nagainst(\"1\") // throws\r\nagainst(undefined) // throws\r\nagainst(false) // throws\r\n```\r\n\r\n#### Primitives as part of objects\r\n\r\nWe can also use the primitive values as part of an object structure pattern. This will enforce the type of the property and that there is a property. If you want a property to be optional, please see [optional properties](#optional-properties) or make it implicitly optional by defining specific values (of type boolean, number or string).\r\n\r\n```ts\r\nconst pattern = {\r\n  name: String,\r\n  age: Number,\r\n  isCool: Boolean\r\n}\r\n\r\nconst against = sanitize(pattern)\r\n\r\n// gotten from a client\r\nconst request = {\r\n  name: \"John\",\r\n  age: 20,\r\n  isCool: true\r\n}\r\n\r\nlet user = against(request)\r\n\r\nuser = {\r\n  name: \"John\",\r\n  age: 20,\r\n  isCool: true\r\n}\r\n\r\nagainst({\r\n  name: \"John\",\r\n  age: \"20\",\r\n  isCool: \"true\"\r\n}) // throws\r\n\r\nagainst({\r\n  name: \"John\",\r\n  age: 20\r\n}) // throws\r\n```\r\n\r\n### Primitive defaults\r\n\r\nYou can specify a default value for a primitive by using specific values as the pattern. This works for numbers, strings and booleans. The default value will be used if the input is `undefined` or `null`. If the input is not `undefined` or `null`, it will be checked against the pattern. If the input is not of the correct type, it will throw.\r\n\r\n```ts\r\nconst against = sanitize(\"default value\")\r\n\r\nagainst(\"anything\") // \"anything\"\r\nagainst(undefined) // \"default value\"\r\n```\r\n\r\nThis works as part of objects analog to the [primitives](#primitives-as-part-of-objects) section.\r\n\r\n### Objects\r\n\r\nYou can use objects as patterns to specify the structure of the input. The input must be an object and must have the same properties as the pattern. If the input is not an object, or if it is missing properties, or if some of the values of the object do not match the nested pattern, it will throw. If the input has more properties than the pattern, they will be ignored.\r\n\r\n```ts\r\nconst pattern = {\r\n  name: String,\r\n  age: Number,\r\n  isCool: true\r\n}\r\n\r\nconst against = sanitize(pattern)\r\n\r\nlet user = against({\r\n  name: \"John\",\r\n  age: 20,\r\n  anotherProp: \"which will be ignored\"\r\n})\r\n\r\nuser = {\r\n  name: \"John\",\r\n  age: 20,\r\n  isCool: true\r\n}\r\n```\r\n\r\nOf course, you can nest objects.\r\n\r\n```ts\r\nconst against = sanitize({\r\n  name: String,\r\n  age: Number,\r\n  isCool: true,\r\n  settings: {\r\n    darkMode: false,\r\n    fontSize: Number\r\n  }\r\n})\r\n```\r\n\r\n#### Optional properties\r\n\r\nYou can add a `?` at the end of a property name to make it explicitly optional. If the property is not set, it will be ignored. If the property is set, it will be checked against the nested pattern (so you can easily make e.g.: an optional Number property without a default, if it is not set). This `?` at the end of a pattern property name does not need to be present in the input object to be sanitized (imagine it like the typescript optional flag, just at runtime). \r\n\r\n```ts\r\nconst against = sanitize({\r\n  \"name?\": String,\r\n  \"age?\": (input) =\u003e {\r\n    if (typeof input !== \"number\") throw new Error(\"Not a number\")\r\n    if (input \u003c 0 || input \u003e 150) throw new Error(\"Not in range\")\r\n    return Math.round(input)\r\n  },\r\n  isCool: true\r\n})\r\n\r\nagainst({\r\n  name: \"John\"\r\n}) // { name: \"John\", isCool: true }\r\n\r\nagainst({\r\n  name: \"John\",\r\n  age: 20\r\n}) // { name: \"John\", age: 20, isCool: true }\r\n\r\nagainst({\r\n  name: \"John\",\r\n  age: -1\r\n}) // throws\r\n\r\nagainst({\r\n  name: \"John\",\r\n  age: 20.1\r\n}) // { name: \"John\", age: 20, isCool: true }\r\n\r\nagainst({}) // { isCool: true }\r\n```\r\n\r\nJust as patterns with default are optional by default, object patterns solely with static properties are optional by default. This means that you could just pass nothing into the `against` function above and get an empty object back (filled with default values ofc if available).\r\n\r\n```ts\r\nagainst() // { isCool: true }\r\n```\r\n\r\n### Combinators\r\n\r\nCombinators are class instances which take patterns as arguments and act as a new, combined, pattern. \r\n\r\n#### OR\r\n\r\nA simple combinator is the `OR` combinator. It takes n patterns and passes if any of them passes. It tries to sanitize the input with each pattern in order. If one of them succeeds, it returns the sanitized value of that pattern. If none of them succeed, it throws. \r\n\r\n```ts\r\nconst pattern = OR(\r\n  Number,\r\n  String\r\n)\r\n\r\nconst against = sanitize(pattern)\r\n\r\nagainst(1) // 1\r\nagainst(\"1\") // \"1\"\r\nagainst(true) // throws\r\nagainst({}) // throws\r\nagainst() // throws\r\n```\r\n\r\n#### AND\r\n\r\nThe `AND` combinator takes `n` patterns and passes if all of them pass. It tries to sanitize the input with each pattern in order. If one of them fails, it throws. If all of them succeed, it returns the sanitized value of the last pattern.\r\n\r\n```ts\r\nconst pattern = AND(\r\n  Number,\r\n  (input) =\u003e {\r\n    if (input \u003c 0) throw new Error(\"Not positive\")\r\n    return input\r\n  }\r\n)\r\n\r\nconst against = sanitize(pattern)\r\n\r\nagainst(1) // 1\r\nagainst(0) // 0\r\nagainst(-1) // throws\r\nagainst(\"1\") // throws\r\n```\r\n\r\n#### NOT\r\n\r\nThe `NOT` combinator takes one pattern and passes if the input does not match the pattern. It tries to sanitize the input with the pattern. If it succeeds, it throws. If it fails, it returns the input.\r\n\r\n```ts\r\nconst pattern = NOT(\r\n  Number\r\n)\r\n\r\nconst against = sanitize(pattern)\r\n\r\nagainst(1) // throws\r\nagainst(\"1\") // \"1\"\r\nagainst(true) // true\r\n```\r\n\r\n#### Combinations\r\n\r\nOf course, you can combine the combinators, with each other, and with objects.\r\n\r\n```ts\r\nconst isPositive = (input) =\u003e {\r\n  if (input \u003c 0) throw new Error(\"Not positive\")\r\n  return input\r\n}\r\n\r\nconst pattern = {\r\n  name: String,\r\n  dob: OR(\r\n    String,\r\n    AND(\r\n      Number,\r\n      (input) =\u003e {\r\n        if (input \u003c 0) throw new Error(\"Not positive\")\r\n        return input\r\n      }\r\n    )\r\n  )\r\n}\r\n\r\nconst against = sanitize(pattern)\r\n\r\nagainst({\r\n  name: \"John\",\r\n  dob: \"1990-01-01\"\r\n}) // { name: \"John\", dob: \"1990-01-01\" }\r\n\r\nagainst({\r\n  name: \"John\",\r\n  dob: 123\r\n}) // { name: \"John\", dob: 123 }\r\n\r\nagainst({\r\n  name: \"John\",\r\n  dob: -1\r\n}) // throws\r\n\r\nagainst({\r\n  name: \"John\"\r\n}) // throws\r\n```\r\n\r\n### Arrays\r\n\r\nNot supported yet.\r\n\r\n\r\n## Polyfills\r\n\r\nThis module requires [`Object.hasOwn`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn#browser_compatibility) (\u003e= Node 16.9.0, \u003e= Chrome 93). If you need to support older environments, you can lazy load all polyfills with the following code:\r\n\r\n```ts\r\nimport { polyfill } from \"sanitize-against\"\r\n\r\nawait polyfill()\r\n```\r\n\r\n\r\n## Contribute\r\n\r\nAll feedback is appreciated. Create a pull request or write an issue.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaximilianmairinger%2Fsanitizeagainst","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaximilianmairinger%2Fsanitizeagainst","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaximilianmairinger%2Fsanitizeagainst/lists"}