{"id":34633009,"url":"https://github.com/luislobo/sails-hook-waterline-safe-criteria","last_synced_at":"2026-05-27T15:03:07.452Z","repository":{"id":317795397,"uuid":"1068872812","full_name":"luislobo/sails-hook-waterline-safe-criteria","owner":"luislobo","description":null,"archived":false,"fork":false,"pushed_at":"2025-10-03T03:51:56.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-12T07:09:22.597Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luislobo.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-03T03:30:52.000Z","updated_at":"2025-10-03T03:52:04.000Z","dependencies_parsed_at":"2025-10-03T05:38:46.620Z","dependency_job_id":"b193c0cd-d6b3-4da2-b195-9d53024bdece","html_url":"https://github.com/luislobo/sails-hook-waterline-safe-criteria","commit_stats":null,"previous_names":["luislobo/sails-hook-waterline-safe-criteria"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/luislobo/sails-hook-waterline-safe-criteria","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislobo%2Fsails-hook-waterline-safe-criteria","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislobo%2Fsails-hook-waterline-safe-criteria/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislobo%2Fsails-hook-waterline-safe-criteria/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislobo%2Fsails-hook-waterline-safe-criteria/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luislobo","download_url":"https://codeload.github.com/luislobo/sails-hook-waterline-safe-criteria/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislobo%2Fsails-hook-waterline-safe-criteria/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33570993,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":"2025-12-24T16:54:32.223Z","updated_at":"2026-05-27T15:03:07.441Z","avatar_url":"https://github.com/luislobo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sails-hook-waterline-safe-criteria\n\nGuard Sails/Waterline queries from silently matching *everything* when unsafe criteria slip through (e.g. `undefined` in a `where` clause or calling `Model.destroy()` with no filters).\n\n[![Socket Badge](https://socket.dev/api/badge/npm/package/sails-hook-waterline-safe-criteria)](https://socket.dev/npm/package/sails-hook-waterline-safe-criteria)\n\n**Key points**\n- Secure by default: once installed the guard is active unless you explicitly disable it.\n- Per-model overrides let you keep legacy behavior where needed.\n- Per-query bypass is available by including `meta: { allowUndefinedWhere: true }` alongside your criteria.\n\n## Why this hook exists\n\nWaterline 0.15 keeps backwards compatibility by stripping undefined values from criteria. In destructive operations that turns this:\n\n```javascript\nawait User.destroy({ where: { status: undefined } });\n```\n\ninto `User.destroy({})` and deletes every record.\n\n`sails-hook-waterline-safe-criteria` adds a guard without forking Waterline. The hook is secure by default; you can opt out globally, per model, or on individual queries when you really need the legacy behavior.\n\n## Requirements\n\n| Dependency | Requirement |\n|------------|-------------|\n| Sails      | ≥ 1.0.0 |\n| Waterline  | 0.15.x (bundled with Sails 1.x) |\n| Node.js    | ≥ 14 (tests run on 16+) |\n\nThe hook inspects stage-one criteria, so it works with any adapter (sails-disk, sails-mongo, sails-postgresql, custom adapters, etc.).\n\n## Local development\n\n1. Install dependencies: `npm install`.\n2. Run the unit/integration suite with `npm test`.\n3. Ensure Docker is available if you plan to exercise the adapter matrix (`npm run test:adapters`).\n4. Use `npm run test:all` before sending changes to cover every scenario.\n\n## Installation\n\n```bash\nnpm install sails-hook-waterline-safe-criteria --save\n```\n\nSails auto-loads any dependency named `sails-hook-*`. If you prefer to be explicit (or to customize the config key) add the hook to `config/hooks.js`:\n\n```javascript\nmodule.exports.hooks = {\n  'waterline-safe-criteria': require('sails-hook-waterline-safe-criteria')\n};\n```\n\n## Configuration\n\n| Scope | Setting | Effect |\n|-------|---------|--------|\n| Global | `config/models.js` → `rejectUndefinedWhere` (default: `true`) | Secure-by-default. Set to `false` if you intentionally want legacy behavior. |\n| Per model | `api/models/Order.js` → `rejectUndefinedWhere: true` | Opt in for specific models only (inherits the global default otherwise). |\n| Hook defaults | `config/waterline-safe-criteria.js` → `{ enabled: false }` | Optional: explicitly opt out of the default guard for the whole app. |\n\n### Per-query bypass\n\nLegacy code paths can still opt in to the old behavior (or you can disable the guard globally by setting `config.models.rejectUndefinedWhere = false`). Add a `meta` object to your stage-one criteria when you really need to bypass the undefined check:\n\n```javascript\nawait Order.destroy({\n  where: criteria,\n  meta: { allowUndefinedWhere: true }\n});\n```\n\nThe guard still requires criteria to exist, but it ignores undefined values when that meta flag is present. The original meta object is reattached to Waterline’s Deferred so adapter-level features (`.meta({ fetch: true })`, etc.) keep working.\n\n## What the guard does\n\nAfter `sails-hook-orm` loads, the hook wraps these helpers on every guarded model:\n\n`find`, `findOne`, `destroy`, `destroyOne`, `update`, `updateOne`, `count`, `sum`, `avg`\n\nEach call goes through the following checks:\n\n1. Criteria must be supplied (no bare `Model.destroy()` / `Model.update()`).\n2. Any `undefined` inside the `where` clause—including nested `and`/`or`/`in` structures—is rejected.\n3. Criteria objects are not mutated; the hook works with Promises or old-school Deferreds alike.\n\nIf an unsafe pattern is detected the guard throws a `flaverr` with code **`E_UNDEFINED_WHERE`**. Example messages:\n\n```\nUnsafe DESTROY on `user` would hit every record. Pass an explicit WHERE or include `meta: { allowUndefinedWhere: true }` to bypass intentionally.\n```\n\n```\nUnsafe UPDATE on `order` detected undefined inside WHERE clause. Undefined values cause Waterline to remove predicates and match everything. Scrub the criteria first, or bypass with `meta: { allowUndefinedWhere: true }`.\n```\n\n### Migration tips\n\n1. **Enable in staging first.** Turn on `rejectUndefinedWhere` globally and run your test suite. Any unsafe queries now fail early.\n2. **Fix or bypass intentionally.** Clean up the criteria or add `meta: { allowUndefinedWhere: true }` where the behavior is desired.\n3. **Roll out per model if needed.** Leave legacy models unguarded (`rejectUndefinedWhere: false`) while protecting everything else.\n\n### Behavior summary\n\n- Throws when criteria are missing (even with bypass meta).\n- Accepts primary-key shorthand (`Model.find(7)` or `Model.destroy([1,2])`).\n- Catches undefined inside nested predicates and arrays.\n- Supports `meta: { allowUndefinedWhere: true }` to bypass only the undefined-value check.\n- Keeps Deferred helpers (`.meta`, `.fetch`) intact.\n\n## Example\n\n```javascript\n// Before (legacy behavior wipes everything)\nawait User.destroy({ where: { status: undefined } });\n\n// After enabling the hook (throws E_UNDEFINED_WHERE)\nawait User.destroy({ where: { status: undefined } });\n\n// If you really need the legacy behavior:\nawait User.destroy({ where: { status: undefined }, meta: { allowUndefinedWhere: true } });\n```\n\n## Testing\n\nRun the core unit + integration suite:\n\n```bash\nnpm test\n```\n\nAdapter coverage (MySQL, PostgreSQL, MongoDB) lives in a separate job because it depends on Docker:\n\n```bash\nnpm run test:adapters\n```\n\nFor everything at once, use:\n\n```bash\nnpm run test:all\n```\n\nThe suites verify the guard on every Waterline helper, nested criteria detection, meta bypass behavior, and three integration scenarios (guarded app, baseline app, per-model configuration). The adapter matrix re-runs the critical happy/sad paths against real adapters.\n\n## Adapter test matrix\n\nA docker-compose file is included for local adapter testing, and the helper script bootstraps/tears down everything automatically:\n\n```bash\nnpm run test:adapters\n```\n\nUnder the hood the script starts the compose stack, waits for each service to be ready, exports the expected connection strings, runs the adapter spec, and shuts everything down. Nothing to configure.\n\n\u003e Tip: `npm run test:all` chains the full suite. `npm run test:adapters:raw` runs just the adapter spec when you already have databases running.\n\n| Variable | Description |\n|----------|-------------|\n| `TEST_MYSQL_URL` | Override MySQL connection string (defaults to compose stack). |\n| `TEST_POSTGRES_URL` | Override PostgreSQL connection string (defaults to compose stack). |\n| `TEST_MONGO_URL` | Override Mongo connection string (defaults to compose stack). |\n\n## Limitations\n\n- Only stage-one criteria are inspected. Raw adapter calls or stage-two tweaks are unaffected.\n- The guard doesn’t attempt to rewrite unsafe queries; it simply throws so you can decide how to fix or bypass.\n- `allowUndefinedWhere` bypasses undefined checks but *not* the “criteria required” rule.\n\n## License\n\nMIT © Luis Lobo Borobia\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluislobo%2Fsails-hook-waterline-safe-criteria","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluislobo%2Fsails-hook-waterline-safe-criteria","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluislobo%2Fsails-hook-waterline-safe-criteria/lists"}