lupaxa-data-poison 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. lupaxa_data_poison-0.1.0/.gitignore +21 -0
  2. lupaxa_data_poison-0.1.0/LICENCE +21 -0
  3. lupaxa_data_poison-0.1.0/PKG-INFO +613 -0
  4. lupaxa_data_poison-0.1.0/README.md +554 -0
  5. lupaxa_data_poison-0.1.0/docs/README.md +23 -0
  6. lupaxa_data_poison-0.1.0/pyproject.toml +100 -0
  7. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/__init__.py +16 -0
  8. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/__main__.py +10 -0
  9. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/catalogue.py +196 -0
  10. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/__init__.py +23 -0
  11. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/csv_doc.py +167 -0
  12. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/dates.py +274 -0
  13. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/emails.py +186 -0
  14. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/filenames.py +236 -0
  15. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/json_doc.py +237 -0
  16. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/numbers.py +347 -0
  17. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/unicode_text.py +364 -0
  18. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/urls.py +276 -0
  19. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/categories/xml_doc.py +159 -0
  20. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/cli.py +518 -0
  21. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/config.py +96 -0
  22. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/engine.py +142 -0
  23. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/errors.py +39 -0
  24. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/limits.py +29 -0
  25. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/manifest.py +130 -0
  26. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/models.py +81 -0
  27. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/mutate.py +253 -0
  28. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/output.py +112 -0
  29. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/profiles.py +77 -0
  30. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/py.typed +0 -0
  31. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/recipe.py +168 -0
  32. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/report.py +94 -0
  33. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/runner.py +250 -0
  34. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/schema_gen.py +304 -0
  35. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/shrink.py +159 -0
  36. lupaxa_data_poison-0.1.0/src/lupaxa/data_poison/version.py +12 -0
@@ -0,0 +1,21 @@
1
+ # OS
2
+ .DS_Store
3
+
4
+ # Cursor
5
+ .cursor/
6
+ .superpowers/
7
+ .worktrees/
8
+ cursor-docs
9
+
10
+ # Make
11
+ .makefiles/
12
+
13
+ # Python
14
+ .venv/
15
+ __pycache__/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .pytest_cache/
19
+ dist/
20
+ build/
21
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) The Lupaxa Project
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,613 @@
1
+ Metadata-Version: 2.5
2
+ Name: lupaxa-data-poison
3
+ Version: 0.1.0
4
+ Summary: Generate, mutate, explain, test and minimise difficult or malformed data.
5
+ Project-URL: Homepage, https://github.com/lupaxa-developers-toolbox/data-poison
6
+ Project-URL: Documentation, https://github.com/lupaxa-developers-toolbox/data-poison
7
+ Project-URL: Repository, https://github.com/lupaxa-developers-toolbox/data-poison
8
+ Project-URL: Issues, https://github.com/lupaxa-developers-toolbox/data-poison/issues
9
+ Author: The Lupaxa Project
10
+ License: MIT License
11
+
12
+ Copyright (c) The Lupaxa Project
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENCE
32
+ Keywords: cli,fuzzing,json,testing,unicode
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Environment :: Console
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: 3.14
43
+ Classifier: Topic :: Software Development :: Testing
44
+ Classifier: Topic :: Utilities
45
+ Requires-Python: >=3.11
46
+ Requires-Dist: defusedxml>=0.7
47
+ Requires-Dist: jsonschema>=4.18
48
+ Provides-Extra: dev
49
+ Requires-Dist: bump-my-version>=1.2.0; extra == 'dev'
50
+ Requires-Dist: hatch>=1.12.0; extra == 'dev'
51
+ Requires-Dist: mypy>=1.12.0; extra == 'dev'
52
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
53
+ Requires-Dist: pytest>=8.0; extra == 'dev'
54
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
55
+ Provides-Extra: test
56
+ Requires-Dist: pytest-cov>=5.0; extra == 'test'
57
+ Requires-Dist: pytest>=8.0; extra == 'test'
58
+ Description-Content-Type: text/markdown
59
+
60
+ <p align="center">
61
+ <a href="https://github.com/lupaxa-developers-toolbox">
62
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/developers-toolbox/readme-logo.png" alt="Developers Toolbox" />
63
+ </a>
64
+ </p>
65
+
66
+ <h1 align="center">Data Poison</h1>
67
+
68
+ Generate, mutate, explain, test, and minimise difficult or malformed data.
69
+ Every case says what it is, why it is unusual, and which behaviour it is
70
+ meant to expose. The same seed produces the same bytes.
71
+
72
+ The PyPI name is `lupaxa-data-poison`. The import path is
73
+ `lupaxa.data_poison`. The console script is `data-poison`. `lupaxa` is a
74
+ namespace package — there is no `lupaxa/__init__.py`.
75
+
76
+ Public names: `generate`, `mutate_payload`, `Case`, `__version__`,
77
+ `get_version()`.
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ pip install lupaxa-data-poison
83
+ ```
84
+
85
+ Requires Python 3.11 or newer. Runtime dependencies are `jsonschema` and
86
+ `defusedxml`. The tool does not use the network.
87
+
88
+ ```bash
89
+ python -m lupaxa.data_poison --version
90
+ ```
91
+
92
+ ## Quick Start
93
+
94
+ ```bash
95
+ data-poison unicode --count 5 --seed 12345
96
+ data-poison explain unicode.zero-width
97
+ data-poison catalogue --category json --validity malformed
98
+ ```
99
+
100
+ ## What it Does
101
+
102
+ A case is a payload plus metadata: identifier, category, generator,
103
+ encoding, validity, contract, explanation, intended behaviour, seed, and
104
+ the generator and catalogue versions. Payloads may be text or raw bytes.
105
+
106
+ Generation reads a built-in catalogue. Profiles choose which risks and
107
+ validity classes are in play. `--case` asks for one named case. Mutation
108
+ changes a copy of a file. A recipe repeats a job from TOML. `test` runs a
109
+ program you name. `shrink` shortens a payload that still fails the same
110
+ way. `replay` returns saved bytes and checks their SHA-256.
111
+
112
+ > **Note:** Shrinking is a heuristic. A smaller payload is not a proof
113
+ > that no smaller one exists. URL and email checks are syntax policies.
114
+ > They do not mean a host or a mailbox exists.
115
+
116
+ ## Validity
117
+
118
+ | Class | Meaning |
119
+ | ----------- | -------------------------------------------------------------------- |
120
+ | `valid` | Allowed by the named contract, and still a likely surprise. |
121
+ | `invalid` | Representable, but it breaks a declared rule in that contract. |
122
+ | `malformed` | It breaks the syntax or encoding of that contract. |
123
+ | `unknown` | The tool will not guess. This is a qualification, not a fourth mode. |
124
+
125
+ The contract is part of the case. A newline can be valid text and invalid
126
+ for one field. JSON `NaN` is malformed JSON and a normal IEEE-754 value
127
+ when the contract is a Python float.
128
+
129
+ ## Command Line
130
+
131
+ | Command | Purpose |
132
+ | ----------- | -------------------------------------------- |
133
+ | `generate` | Cases from the whole catalogue |
134
+ | `unicode` | Text and encoding edges |
135
+ | `numbers` | Integers, floats, decimals, and JSON numbers |
136
+ | `dates` | Calendar and timezone edges |
137
+ | `urls` | URL syntax, with no DNS |
138
+ | `emails` | Address syntax, with no SMTP |
139
+ | `filenames` | Name strings, never created on disk |
140
+ | `json` | Valid and malformed JSON documents |
141
+ | `csv` | One strict comma dialect |
142
+ | `xml` | XML with entity expansion disabled |
143
+ | `schema` | A documented JSON Schema subset |
144
+ | `mutate` | A copy of an existing file |
145
+ | `recipe` | A TOML job |
146
+ | `test` | A local program, not a sandbox |
147
+ | `shrink` | A smaller payload with the same failure |
148
+ | `replay` | Saved bytes and a hash check |
149
+ | `explain` | One catalogue entry |
150
+ | `catalogue` | Filter the catalogue |
151
+ | `profiles` | The five profiles |
152
+ | `inspect` | A manifest, without dumping payloads |
153
+ | `report` | A saved test report |
154
+
155
+ `--help` works on the root command and on every subcommand. `--version`
156
+ prints `data-poison x.y.z`.
157
+
158
+ | Flag | Default | Meaning |
159
+ | ----------------------- | ---------------------------------------- | ------------------------------------------------------------------------- |
160
+ | `--count` | `1` | How many cases. |
161
+ | `--seed` | chosen and stored | Repeatable selection. |
162
+ | `--profile` | `awkward` | `mild`, `awkward`, `hostile`, `nightmare`, or `apocalypse`. |
163
+ | `--case` | | One catalogue identifier or a unique suffix. |
164
+ | `--category` | the command name | Comma-separated categories for `generate` and `test`. |
165
+ | `--validity` | profile | `valid`, `invalid`, `malformed`, or `all`. |
166
+ | `--format` | `text`, or `jsonl` when count is above 1 | `text`, `json`, `jsonl`, `raw`, `hex`, `base64`. |
167
+ | `--output` | stdout | File or directory. |
168
+ | `--manifest` | | JSON manifest path. |
169
+ | `--max-bytes` | 1 MiB | Per-case ceiling, hard stop 32 MiB. |
170
+ | `--max-depth` | 32 | Nesting ceiling, hard stop 128. |
171
+ | `--max-total-bytes` | 100 MiB | Run ceiling, hard stop 512 MiB. |
172
+ | `--quiet` / `--verbose` | quiet | Errors still go to stderr. |
173
+ | `--force` | off | Overwrite an existing output file. |
174
+ | `--frame length-prefix` | | Several raw payloads on one stream: 4-byte big-endian length, then bytes. |
175
+
176
+ A single `--format raw` write is the payload bytes only. No newline and no
177
+ label. Several raw cases need a directory or `--frame length-prefix`.
178
+
179
+ | Exit | Meaning |
180
+ | ----- | ---------------------------------------------------------- |
181
+ | `0` | Finished. |
182
+ | `1` | Operational error, or shrink could not keep the failure. |
183
+ | `2` | Bad arguments or configuration. |
184
+ | `3` | Input, parse, or validation error. |
185
+ | `4` | A safety ceiling. |
186
+ | `5` | The test run found a crash, timeout, or unexpected result. |
187
+ | `6` | Replay hash or integrity failure. |
188
+ | `130` | Interrupted. |
189
+
190
+ Expected invalid data does not make generation fail. A non-zero program
191
+ exit is a rejection, not a crash. A crash is a signal.
192
+
193
+ ## Profiles
194
+
195
+ `mild` is empty values, whitespace, and small boundaries. `awkward` is
196
+ valid input that still surprises. `hostile` is invalid and malformed.
197
+ `nightmare` mixes those and stacks mutations in the profile description.
198
+ `apocalypse` pushes size and depth up to the ceilings you set, never past
199
+ the hard caps.
200
+
201
+ ```bash
202
+ data-poison profiles
203
+ data-poison profiles show nightmare
204
+ data-poison generate --profile apocalypse --count 10 --seed 1
205
+ ```
206
+
207
+ ## Catalogue
208
+
209
+ Identifiers stay put until the meaning changes. `unicode.zero-width` is
210
+ the stable name. Fixed cases return the same bytes every time.
211
+ Parameterised cases may use the seed and the limits.
212
+
213
+ ```bash
214
+ data-poison catalogue
215
+ data-poison catalogue --category unicode
216
+ data-poison catalogue --validity malformed
217
+ data-poison catalogue --tag boundary
218
+ data-poison explain unicode.normalisation.nfc-nfd
219
+ ```
220
+
221
+ Text and JSON catalogue output are both supported.
222
+
223
+ ## Unicode
224
+
225
+ The catalogue includes empty strings, whitespace, newlines, non-breaking
226
+ and zero-width characters, joiners, combining marks, NFC/NFD and
227
+ NFKC/NFKD samples, bidi controls, right-to-left text, confusables, emoji
228
+ sequences, skin tones, regional indicators, variation selectors,
229
+ supplementary-plane characters, a noncharacter, long strings, NUL and
230
+ ASCII controls, line and paragraph separators, Turkish I, sharp S, mixed
231
+ scripts, and invalid, truncated, and surrogate UTF-8.
232
+
233
+ Confusable characters are not treated as the same character. Normalisation
234
+ form names are NFC, NFD, NFKC, and NFKD. Explanations name code points
235
+ when Python knows the name.
236
+
237
+ ```bash
238
+ data-poison unicode --count 20 --seed 1
239
+ data-poison unicode --case zero-width
240
+ data-poison unicode --profile nightmare --count 5
241
+ ```
242
+
243
+ ## Numbers
244
+
245
+ Modes are integer, float, decimal, text, and JSON. `--bits` and
246
+ `--signed` / `--no-signed` apply to integer bounds. `--minimum` and
247
+ `--maximum` apply to the outside-range case. `--precision` and `--scale`
248
+ are accepted for decimal work and ignored by cases that do not use them.
249
+ `--bits` with `--numeric-mode float` is an error.
250
+
251
+ JSON number cases never emit `NaN` or `Infinity`. Those tokens exist as
252
+ malformed JSON and, separately, as IEEE-754 text.
253
+
254
+ ```bash
255
+ data-poison numbers --case float-precision
256
+ data-poison numbers --case integer-bounds --bits 32 --signed
257
+ data-poison numbers --case outside-range --minimum 0 --maximum 100
258
+ data-poison numbers --case json-overflow
259
+ ```
260
+
261
+ ## Dates
262
+
263
+ Leap day, non-leap 29 February, month and year boundaries, midnight, hour
264
+ 24, offsets, fractional seconds, ISO and RFC 3339 samples, epoch values,
265
+ date-only and time-only values, and naive versus aware timestamps are in
266
+ the catalogue.
267
+
268
+ `--timezone` selects an IANA zone for `dates.dst-gap` and
269
+ `dates.dst-overlap`. The gap and overlap come from that zone's 2024
270
+ transitions. The defaults are `Europe/London` and `America/New_York`. A
271
+ zone with no such transition, or a machine without timezone data, is an
272
+ error rather than a made-up clock change.
273
+
274
+ ```bash
275
+ data-poison dates --case leap-year
276
+ data-poison dates --case dst-gap --timezone Europe/London
277
+ data-poison dates --case dst-overlap --timezone America/New_York
278
+ ```
279
+
280
+ ## URLs
281
+
282
+ No DNS and no HTTP. The contracts are `absolute-url`,
283
+ `relative-reference`, and `url-syntax`. A syntactically absolute URL can
284
+ still be one an application should reject, such as `javascript:`.
285
+
286
+ ```bash
287
+ data-poison urls --count 10 --seed 1
288
+ data-poison urls --case percent-encoding
289
+ data-poison urls --case ipv6
290
+ ```
291
+
292
+ ## Emails
293
+
294
+ Policies are `conservative` (everyday ASCII, plus-tags allowed),
295
+ `standards` (a documented subset that allows a quoted local part and
296
+ Unicode), and `ascii` (non-ASCII is invalid). None of them send mail.
297
+
298
+ ```bash
299
+ data-poison emails --profile hostile --count 5 --seed 1
300
+ data-poison emails --case unicode-local --policy ascii
301
+ data-poison emails --case quoted-local --policy standards
302
+ ```
303
+
304
+ ## Filenames
305
+
306
+ Cases are strings. They are not created, and they are not used as the
307
+ `--output` path. Contracts cover POSIX names, POSIX paths, Windows names,
308
+ Windows paths, and macOS normalisation. `--platform` is `posix`,
309
+ `windows`, or `macos`.
310
+
311
+ ```bash
312
+ data-poison filenames --platform windows --case reserved-name
313
+ data-poison filenames --case unicode-collision
314
+ ```
315
+
316
+ ## JSON, CSV, and XML
317
+
318
+ Valid JSON is parsed by the standard library. Malformed JSON is emitted
319
+ as raw text and is not repaired. Duplicate keys are valid syntax and an
320
+ interoperability hazard, not a syntax error.
321
+
322
+ CSV uses comma, double quote, and LF. A header is part of that contract.
323
+ Values that start with `=`, `+`, `-`, or `@` are flagged because a
324
+ spreadsheet may treat them as formulas. This tool does not open them.
325
+
326
+ XML parsing uses `defusedxml`. External entities and expansion are not
327
+ resolved. The entity-expansion fixture is a few characters, not a large
328
+ bomb. Deep nesting stops at `--max-depth`.
329
+
330
+ ```bash
331
+ data-poison json --validity valid --count 10 --seed 1
332
+ data-poison json --validity malformed --count 5 --seed 1
333
+ data-poison json --case duplicate-keys
334
+ data-poison csv --case embedded-newline
335
+ data-poison xml --case mismatched-tags
336
+ data-poison xml --case entity-expansion
337
+ ```
338
+
339
+ ## Schema
340
+
341
+ `schema` reads a local JSON Schema document. The supported Draft 2020-12
342
+ subset is `type`, `properties`, `required`, boolean `additionalProperties`,
343
+ `items`, size and length bounds, `minimum` and `maximum` including the
344
+ exclusive forms, `multipleOf`, `enum`, `const`, a short `pattern`,
345
+ `format` of `email`, `date-time`, `uri`, `uuid`, `ipv4`, and `ipv6`,
346
+ property counts, `uniqueItems`, and local `$defs` / `$ref`.
347
+
348
+ Anything else that would change validation is an error in the default
349
+ strict mode. `--permissive` still generates and marks validity `unknown`.
350
+ Remote `$ref` is never fetched.
351
+
352
+ Modes are `valid`, `invalid`, `boundary`, and `mixed`. Invalid cases aim
353
+ at one constraint. Each case records the expected result, the validator
354
+ result, and the constraint name.
355
+
356
+ ```bash
357
+ data-poison schema customer.schema.json --count 20 --seed 1
358
+ data-poison schema customer.schema.json --mode boundary
359
+ ```
360
+
361
+ ## Recipes
362
+
363
+ Paths in a recipe are relative to the recipe file. Unknown keys, bad
364
+ types, a minimum above a maximum, and a missing schema path fail
365
+ validation. `--count` and `--seed` on the command replace the recipe.
366
+
367
+ ```toml
368
+ [recipe]
369
+ name = "Customer Registration"
370
+ description = "Difficult customer fields"
371
+ count = 20
372
+ seed = 12345
373
+ profile = "nightmare"
374
+
375
+ [fields.username]
376
+ type = "string"
377
+ poison = ["unicode", "whitespace", "length"]
378
+
379
+ [fields.age]
380
+ type = "integer"
381
+ minimum = 0
382
+ maximum = 120
383
+ poison = ["boundaries", "wrong-type", "null"]
384
+
385
+ [fields.email]
386
+ type = "email"
387
+ poison = ["format"]
388
+
389
+ [output]
390
+ format = "jsonl"
391
+ ```
392
+
393
+ ```bash
394
+ data-poison recipe customer.toml --validate
395
+ data-poison recipe customer.toml --seed 999
396
+ ```
397
+
398
+ Field poison values are `unicode`, `whitespace`, `length`, `boundaries`,
399
+ `wrong-type`, `null`, and `format`.
400
+
401
+ ## Mutation
402
+
403
+ `mutate` reads a file and writes new payloads. It does not open the
404
+ source for writing. Modes are `semantic` (JSON shape kept, then
405
+ re-serialised), `syntax` (deliberately broken), and `bytes`. Strategies
406
+ are `single`, `combined`, `systematic`, and `random`.
407
+
408
+ The strategy names include character and byte edits, whitespace and
409
+ controls, Unicode substitution, truncation, field removal and
410
+ duplication, type and null swaps, numeric and length changes, array
411
+ edits, nesting, and delimiter, quote, escape, and encoding corruption.
412
+
413
+ ```bash
414
+ data-poison mutate customer.json --strategy single --count 10 --seed 1
415
+ data-poison mutate customer.json --mode syntax --output mutations/
416
+ data-poison mutate input.bin --mode bytes --count 5 --seed 1
417
+ ```
418
+
419
+ ## Test Runner
420
+
421
+ You name the command. The default delivery is stdin. `--input-mode file`
422
+ writes a temporary file and deletes it afterwards. `{input}` is replaced
423
+ as one argument. The command is split with `shlex` and run with
424
+ `shell` left off, so the payload is not a shell script.
425
+
426
+ A timeout, a captured-output cap, and the case limit apply. A non-zero
427
+ exit is `rejected_expected` when the case is invalid or malformed, and
428
+ `unexpected_rejection` when the case is valid. Exit 0 on invalid input is
429
+ `unexpected_acceptance`. A signal is `crash`. Expiry is `timeout`. A
430
+ missing program is `execution_error`. Anything the oracle cannot judge is
431
+ `inconclusive`.
432
+
433
+ ```bash
434
+ data-poison test --command "./validate-customer" --category json --count 20 --seed 1
435
+ data-poison test --command "./parser {input}" --input-mode file --count 5 --seed 1
436
+ ```
437
+
438
+ > **Note:** The runner is not a security sandbox. It runs the command you
439
+ > typed, with your user privileges.
440
+
441
+ ## Shrinking
442
+
443
+ `shrink` keeps a predicate: `same-exit`, `same-signal`, `timeout`, or
444
+ `nonzero`. It deletes byte ranges, caches candidates by SHA-256, and
445
+ stops at `--attempts`. The original file is only read. The report gives
446
+ the old size, the new size, the reduction, and the attempt count, and it
447
+ says the result is not mathematically minimal.
448
+
449
+ ```bash
450
+ data-poison shrink failing.bin --command "./validate-json" --predicate same-exit --output minimal.bin
451
+ ```
452
+
453
+ ## Replay and Manifests
454
+
455
+ A manifest records version, case id, seed, category, generator, validity,
456
+ contract, mutations, encoding, length, and SHA-256. Payloads are inline
457
+ base64 or a sibling `.bin` whose path cannot leave the manifest
458
+ directory. `replay --verify` checks the hash. Saved bytes stay replayable
459
+ even if a later generator changes. A hash mismatch exits `6`.
460
+
461
+ ```bash
462
+ data-poison replay cases/manifest.json --verify --format raw --output reproduced.bin
463
+ data-poison inspect cases/manifest.json
464
+ ```
465
+
466
+ ## Reports
467
+
468
+ `test` can write `--report results.json`. `report` prints text, JSON, or
469
+ Markdown: counts by validity and by result class, durations, and total
470
+ bytes. There is no single quality score.
471
+
472
+ ```bash
473
+ data-poison report results.json --format markdown
474
+ ```
475
+
476
+ ## Output Formats
477
+
478
+ `text` escapes control characters. `json` and `jsonl` carry base64 plus
479
+ hex so binary payloads are not replaced with U+FFFD. `hex` and `base64`
480
+ are one payload per line. `raw` is the exact bytes.
481
+
482
+ ## Safety Limits
483
+
484
+ Defaults are one case, profile `awkward`, 1 MiB per payload, depth 32,
485
+ and 100 MiB in total. You can raise them. The hard ceilings are 100,000
486
+ cases, 32 MiB per payload, depth 128, and 512 MiB in total. Existing
487
+ files are not overwritten without `--force`. Mutation inputs are not
488
+ modified. Manifests and reports are written by replacing a temporary
489
+ file. Temporary command inputs are removed unless
490
+ `keep_temporary` is set in the config.
491
+
492
+ ## Configuration
493
+
494
+ TOML. Precedence is the command line, then the recipe, then `--config`,
495
+ then `~/.config/data-poison/config.toml`, then built-in defaults. Unknown
496
+ keys are an error.
497
+
498
+ ```toml
499
+ profile = "awkward"
500
+ count = 1
501
+ format = "text"
502
+ max_bytes = 1048576
503
+ timeout_seconds = 5
504
+ expect = "by-validity"
505
+ ```
506
+
507
+ `expect` may be `by-validity`, `accept`, or `reject`.
508
+
509
+ ## Platform Differences
510
+
511
+ Filename contracts differ for Windows, POSIX, and macOS. They describe
512
+ operating-system name rules, not one vendor's filesystem. Timezone cases
513
+ need IANA data; if it is missing, the command says so. Process signals
514
+ and process-tree kill behaviour follow the host. The tool does not claim
515
+ identical signal numbers on Windows.
516
+
517
+ ## Python API
518
+
519
+ ```python
520
+ from lupaxa.data_poison import generate
521
+
522
+ cases = generate(count=3, seed=12345, categories=["unicode"], profile="awkward")
523
+ print(cases[0].explanation)
524
+ print(cases[0].payload)
525
+ ```
526
+
527
+ Generators return `Case` values. They do not print. `mutate_payload`
528
+ works on bytes the same way.
529
+
530
+ ## Troubleshooting
531
+
532
+ `unknown case` means the identifier or suffix matched nothing, or matched
533
+ more than one entry. Use `catalogue` and the full identifier. `unsupported
534
+ schema keyword` means strict mode refused a feature outside the subset.
535
+ `timezone ... is not available` means the IANA database has no such zone.
536
+ `already exists` means pass `--force`. A hash error on replay means the
537
+ payload bytes and the manifest disagree; the bytes were not rewritten to
538
+ match.
539
+
540
+ ## Examples
541
+
542
+ Difficult usernames:
543
+
544
+ ```bash
545
+ data-poison unicode --profile nightmare --count 20 --seed 12345 --format jsonl --output usernames.jsonl
546
+ ```
547
+
548
+ Numeric boundaries:
549
+
550
+ ```bash
551
+ data-poison numbers --case outside-range --minimum 0 --maximum 100 --seed 1
552
+ ```
553
+
554
+ Malformed JSON, one file per case:
555
+
556
+ ```bash
557
+ data-poison json --validity malformed --count 10 --seed 1 --format raw --output malformed-cases/
558
+ ```
559
+
560
+ Schema boundaries:
561
+
562
+ ```bash
563
+ data-poison schema customer.schema.json --mode boundary --count 20 --seed 1 --manifest cases.manifest.json
564
+ ```
565
+
566
+ Mutate a JSON file:
567
+
568
+ ```bash
569
+ data-poison mutate customer.json --strategy single --count 20 --seed 1 --output mutations/
570
+ ```
571
+
572
+ Run a local checker:
573
+
574
+ ```bash
575
+ data-poison test --command "./validate-json" --category json --profile hostile --count 50 --seed 12345 --report results.json
576
+ ```
577
+
578
+ Replay:
579
+
580
+ ```bash
581
+ data-poison replay cases/manifest.json --verify --format raw --output reproduced.bin
582
+ ```
583
+
584
+ Shrink:
585
+
586
+ ```bash
587
+ data-poison shrink failing.bin --command "./validate-json" --predicate nonzero --output minimal.bin
588
+ ```
589
+
590
+ A recipe:
591
+
592
+ ```bash
593
+ data-poison recipe customer.toml --validate
594
+ data-poison recipe customer.toml --seed 12345
595
+ ```
596
+
597
+ A Markdown report:
598
+
599
+ ```bash
600
+ data-poison report results.json --format markdown
601
+ ```
602
+
603
+ ## Development
604
+
605
+ ```bash
606
+ make init
607
+ make python-install-dev
608
+ make python-check
609
+ ```
610
+
611
+ <a href="https://github.com/the-lupaxa-project">
612
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
613
+ </a>