shadow-vault 0.3.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 (37) hide show
  1. shadow_vault-0.3.0/.github/ISSUE_TEMPLATE/01_add_brazilian_cpf.md +37 -0
  2. shadow_vault-0.3.0/.github/ISSUE_TEMPLATE/02_add_uk_nino.md +40 -0
  3. shadow_vault-0.3.0/.github/ISSUE_TEMPLATE/03_add_eu_iban.md +44 -0
  4. shadow_vault-0.3.0/.github/ISSUE_TEMPLATE/config.yml +9 -0
  5. shadow_vault-0.3.0/.github/workflows/ci.yml +40 -0
  6. shadow_vault-0.3.0/.github/workflows/publish.yml +42 -0
  7. shadow_vault-0.3.0/.gitignore +15 -0
  8. shadow_vault-0.3.0/CONTRIBUTING.md +175 -0
  9. shadow_vault-0.3.0/LICENSE +21 -0
  10. shadow_vault-0.3.0/PKG-INFO +305 -0
  11. shadow_vault-0.3.0/README.md +262 -0
  12. shadow_vault-0.3.0/benchmarks/benchmark_results.md +26 -0
  13. shadow_vault-0.3.0/benchmarks/run_benchmarks.py +322 -0
  14. shadow_vault-0.3.0/examples/demo_agent_roundtrip.py +249 -0
  15. shadow_vault-0.3.0/pyproject.toml +84 -0
  16. shadow_vault-0.3.0/shadow_vault/__init__.py +75 -0
  17. shadow_vault-0.3.0/shadow_vault/detectors.py +490 -0
  18. shadow_vault-0.3.0/shadow_vault/engine.py +217 -0
  19. shadow_vault-0.3.0/shadow_vault/integrations/__init__.py +21 -0
  20. shadow_vault-0.3.0/shadow_vault/integrations/decorators.py +92 -0
  21. shadow_vault-0.3.0/shadow_vault/integrations/langchain.py +291 -0
  22. shadow_vault-0.3.0/shadow_vault/integrations/mcp_proxy.py +131 -0
  23. shadow_vault-0.3.0/shadow_vault/integrations/proxy_server.py +162 -0
  24. shadow_vault-0.3.0/shadow_vault/store.py +342 -0
  25. shadow_vault-0.3.0/shadow_vault/stream.py +120 -0
  26. shadow_vault-0.3.0/shadow_vault/synthesizer.py +140 -0
  27. shadow_vault-0.3.0/tests/__init__.py +1 -0
  28. shadow_vault-0.3.0/tests/test_decorators.py +84 -0
  29. shadow_vault-0.3.0/tests/test_detectors.py +303 -0
  30. shadow_vault-0.3.0/tests/test_engine.py +186 -0
  31. shadow_vault-0.3.0/tests/test_langchain.py +272 -0
  32. shadow_vault-0.3.0/tests/test_mcp_proxy.py +134 -0
  33. shadow_vault-0.3.0/tests/test_proxy_server.py +182 -0
  34. shadow_vault-0.3.0/tests/test_shadow_vault.py +310 -0
  35. shadow_vault-0.3.0/tests/test_store.py +268 -0
  36. shadow_vault-0.3.0/tests/test_stream.py +137 -0
  37. shadow_vault-0.3.0/tests/test_synthesizer.py +110 -0
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: "[Good First Issue] Add Brazilian CPF Detector"
3
+ about: Add a regional detector for Brazilian CPF (Cadastro de Pessoas Físicas) with Modulo 11 checksum validation.
4
+ title: "[Detector Request]: Add Brazilian CPF Detector (Modulo 11)"
5
+ labels: ["enhancement", "good first issue", "detectors", "help wanted"]
6
+ assignees: []
7
+ ---
8
+
9
+ ## Summary
10
+ Add support for detecting and de-identifying **Brazilian CPF** (Cadastro de Pessoas Físicas) numbers using regex matching paired with official **Modulo 11** dual check-digit verification.
11
+
12
+ ## Background & Specifications
13
+ - **Format**: 11 digits, commonly formatted as `XXX.XXX.XXX-XX` (e.g., `123.456.789-09`) or raw 11 digits `XXXXXXXXXXX`.
14
+ - **Validation Algorithm**:
15
+ 1. CPF cannot consist of 11 identical digits (e.g., `111.111.111-11` is invalid).
16
+ 2. First check digit (10th digit): `sum(d[i] * (10 - i) for i in range(9)) * 10 % 11` (if 10, check digit is 0).
17
+ 3. Second check digit (11th digit): `sum(d[i] * (11 - i) for i in range(10)) * 10 % 11` (if 10, check digit is 0).
18
+ - **Synthetic Token Format**:
19
+ - Must preserve format: `f"999.888.{num:03d}-{check:02d}"` or deterministic synthetic pattern that passes format checkers and is recognized by `StreamHydrator`.
20
+
21
+ ## Implementation Checklist
22
+ - [ ] Add `EntityType.CPF = "CPF"` to `shadow_vault/detectors.py`
23
+ - [ ] Implement `is_cpf_valid(cpf_str: str) -> bool` using Modulo 11
24
+ - [ ] Implement `detect_cpf(text: str) -> list[PIIEntity]` with compiled regex
25
+ - [ ] Register `detect_cpf` inside `detect_entities()`
26
+ - [ ] Implement synthetic token generation for `EntityType.CPF` in `shadow_vault/synthesizer.py`
27
+ - [ ] Add unit tests in `tests/test_detectors.py`:
28
+ - Valid CPF with dots and dash
29
+ - Valid unformatted CPF (11 digits)
30
+ - Invalid check digits (must be rejected)
31
+ - All identical digits (e.g. `000.000.000-00`, must be rejected)
32
+ - Round-trip `dehydrate()` and `hydrate()` test
33
+ - [ ] Verify test suite passes: `pytest -v`
34
+
35
+ ## Reference Code
36
+ See [CONTRIBUTING.md](https://github.com/dhirajparihar/shadow-vault/blob/main/CONTRIBUTING.md) for a complete reference implementation in under 20 lines of code!
37
+
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: "[Good First Issue] Add UK National Insurance Number (NINO) Detector"
3
+ about: Add a regional detector for UK National Insurance Numbers (NINO) with strict prefix and suffix validation.
4
+ title: "[Detector Request]: Add UK National Insurance Number (NINO) Detector"
5
+ labels: ["enhancement", "good first issue", "detectors", "help wanted"]
6
+ assignees: []
7
+ ---
8
+
9
+ ## Summary
10
+ Add support for detecting and de-identifying **UK National Insurance Numbers (NINO)** used in the United Kingdom for taxation, social security, and national healthcare administration.
11
+
12
+ ## Background & Specifications
13
+ - **Format**: 9 characters consisting of 2 prefix letters, 6 digits, and 1 suffix letter (e.g., `QQ 12 34 56 A` or `QQ123456A`).
14
+ - **Official HMRC Formatting Rules**:
15
+ 1. **First letter**: Any letter except `D`, `F`, `I`, `Q`, `U`, `V`.
16
+ 2. **Second letter**: Any letter except `D`, `F`, `I`, `O`, `Q`, `U`, `V`.
17
+ 3. **Disallowed prefixes**: Combinations `BG`, `GB`, `KN`, `NK`, `NT`, `TN`, `ZZ` are administrative or reserved and not valid citizen NINOs.
18
+ 4. **Middle 6 characters**: Digits `0-9`.
19
+ 5. **Suffix letter**: Must be `A`, `B`, `C`, `D` (or space in legacy records).
20
+ - **Synthetic Token Format**:
21
+ - Must preserve format: e.g., `f"SH {short_hash_digits:06d} Z"` or `f"SV{d6}A"`.
22
+
23
+ ## Implementation Checklist
24
+ - [ ] Add `EntityType.UK_NINO = "UK_NINO"` to `shadow_vault/detectors.py`
25
+ - [ ] Implement `is_uk_nino_valid(nino_str: str) -> bool` checking HMRC character rules
26
+ - [ ] Implement `detect_uk_nino(text: str) -> list[PIIEntity]` with compiled regex
27
+ - [ ] Register `detect_uk_nino` inside `detect_entities()`
28
+ - [ ] Implement format-preserving synthetic token generation in `shadow_vault/synthesizer.py`
29
+ - [ ] Add unit tests in `tests/test_detectors.py`:
30
+ - Valid NINO with spaces: `"QQ 12 34 56 A"`
31
+ - Valid NINO without spaces: `"AB123456C"`
32
+ - Invalid prefix characters (e.g. starting with `D` or `F`)
33
+ - Disallowed combination (e.g. starting with `GB` or `ZZ`)
34
+ - Invalid suffix letter (e.g. ending in `X` or `Z`)
35
+ - Round-trip `dehydrate()` and `hydrate()` test
36
+ - [ ] Verify test suite passes: `pytest -v`
37
+
38
+ ## Reference Code
39
+ See [CONTRIBUTING.md](https://github.com/dhirajparihar/shadow-vault/blob/main/CONTRIBUTING.md) for how to add a regional detector in under 20 lines of code!
40
+
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: "[Good First Issue] Add EU / International IBAN Detector"
3
+ about: Add a detector for International Bank Account Numbers (IBAN) with ISO 7064 MOD-97 checksum validation.
4
+ title: "[Detector Request]: Add EU / International IBAN Detector (MOD-97)"
5
+ labels: ["enhancement", "good first issue", "detectors", "help wanted"]
6
+ assignees: []
7
+ ---
8
+
9
+ ## Summary
10
+ Add support for detecting and de-identifying **International Bank Account Numbers (IBAN)** across European and global financial systems using regex pattern matching verified by the official **ISO 7064 MOD 97-10** checksum algorithm.
11
+
12
+ ## Background & Specifications
13
+ - **Format**: Up to 34 alphanumeric characters (standard EU IBANs are typically 15–31 characters, e.g., Germany `DE89 3704 0044 0532 0130 00`, France `FR14 2004 1010 0505 0001 3M02 606`).
14
+ - **Structure**:
15
+ 1. 2-letter ISO 3166-1 country code (e.g., `DE`, `FR`, `GB`, `ES`, `IT`).
16
+ 2. 2 check digits (values `02` through `98`).
17
+ 3. Up to 30 alphanumeric Basic Bank Account Number (BBAN) characters.
18
+ - **Validation Algorithm (ISO 7064 MOD 97-10)**:
19
+ 1. Remove spaces and convert to uppercase.
20
+ 2. Move the first 4 characters (country code + 2 check digits) to the end of the string.
21
+ 3. Replace each letter with two digits (`A` -> `10`, `B` -> `11`, ..., `Z` -> `35`).
22
+ 4. Interpret the string as a decimal integer: compute `int(digits) % 97`.
23
+ 5. The IBAN is valid if and only if `remainder == 1`.
24
+ - **Synthetic Token Format**:
25
+ - Must preserve format: e.g. `f"DE99SHDW{short_hash[:10]}00"` with valid ISO MOD-97 check digits so banking APIs accept it.
26
+
27
+ ## Implementation Checklist
28
+ - [ ] Add `EntityType.IBAN = "IBAN"` to `shadow_vault/detectors.py`
29
+ - [ ] Implement `is_iban_valid(iban_str: str) -> bool` using the ISO 7064 MOD 97-10 algorithm
30
+ - [ ] Implement `detect_iban(text: str) -> list[PIIEntity]` with compiled regex and length validation
31
+ - [ ] Register `detect_iban` inside `detect_entities()`
32
+ - [ ] Implement format-preserving synthetic token generation in `shadow_vault/synthesizer.py`
33
+ - [ ] Add unit tests in `tests/test_detectors.py`:
34
+ - Valid German IBAN with spaces
35
+ - Valid French IBAN without spaces
36
+ - Valid British IBAN
37
+ - Corrupted check digits (must be rejected)
38
+ - Non-IBAN alphanumeric strings (must not produce false positives)
39
+ - Round-trip `dehydrate()` and `hydrate()` test
40
+ - [ ] Verify test suite passes: `pytest -v`
41
+
42
+ ## Reference Code
43
+ See [CONTRIBUTING.md](https://github.com/dhirajparihar/shadow-vault/blob/main/CONTRIBUTING.md) for how to add a regional detector in under 20 lines of code!
44
+
@@ -0,0 +1,9 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Shadow Vault Documentation & Contributing Guide
4
+ url: https://github.com/dhirajparihar/shadow-vault/blob/main/CONTRIBUTING.md
5
+ about: Read our guide on how to add a regional PII detector in under 20 lines of code.
6
+ - name: Benchmark & Performance Inquiries
7
+ url: https://github.com/dhirajparihar/shadow-vault/blob/main/benchmarks/benchmark_results.md
8
+ about: View latency, throughput, and memory footprint benchmarks.
9
+
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest]
17
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
18
+
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ cache: "pip"
28
+
29
+ - name: Install dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ python -m pip install -e ".[all]"
33
+
34
+ - name: Run test suite with pytest
35
+ run: |
36
+ pytest -v
37
+
38
+ - name: Verify CLI demo execution
39
+ run: |
40
+ python examples/demo_agent_roundtrip.py
@@ -0,0 +1,42 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ push:
7
+ tags:
8
+ - "v*"
9
+
10
+ jobs:
11
+ build-and-publish:
12
+ name: Build distribution and publish to PyPI
13
+ runs-on: ubuntu-latest
14
+ environment:
15
+ name: pypi
16
+ url: https://pypi.org/p/shadow-vault
17
+ permissions:
18
+ id-token: write # Mandatory for PyPI Trusted Publishing (OIDC)
19
+ contents: read
20
+
21
+ steps:
22
+ - name: Checkout source code
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Install build tools
31
+ run: |
32
+ python -m pip install --upgrade pip
33
+ python -m pip install build
34
+
35
+ - name: Build sdist and wheel
36
+ run: |
37
+ python -m build
38
+
39
+ - name: Publish package distributions to PyPI
40
+ uses: pypa/gh-action-pypi-publish@release/v1
41
+ with:
42
+ packages-dir: dist/
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
10
+ *.db
11
+ *.sqlite3
12
+ .venv/
13
+ env/
14
+ venv/
15
+ .DS_Store
@@ -0,0 +1,175 @@
1
+ # Contributing to Shadow Vault
2
+
3
+ Thank you for your interest in contributing to **Shadow Vault**! We are building an open-source, ultra-low-latency, format-preserving PII de-identification and re-hydration engine for AI agents, LangChain/LangGraph, and MCP tools.
4
+
5
+ Whether you are fixing a bug, improving test coverage, or adding support for your country's national identity numbers, your contributions are welcome.
6
+
7
+ ---
8
+
9
+ ## Code of Conduct & Philosophy
10
+
11
+ Shadow Vault adheres to these engineering tenets:
12
+ 1. **Zero External Machine Learning / Torch Dependencies in Core**: Detection must be fast, deterministic, and lightweight (< 1ms execution). We rely on compiled regular expressions and mathematical check-digit algorithms (e.g., Luhn, Verhoeff, Modulo 11).
13
+ 2. **Format-Preserving Realism**: Synthetic replacement tokens must look structurally indistinguishable from real data so downstream Pydantic schemas, regexes, and database columns do not break.
14
+ 3. **Deterministic & Reversible**: Every token must be coreference-consistent within a session and fully reversible when tools need real data.
15
+ 4. **Zero Placeholders**: All contributed code must be fully tested, production-ready, and accompanied by comprehensive unit tests.
16
+
17
+ ---
18
+
19
+ ## Local Development Setup
20
+
21
+ Shadow Vault requires **Python 3.10+**.
22
+
23
+ ### 1. Clone & Set Up Virtual Environment
24
+
25
+ ```bash
26
+ # Clone the repository
27
+ git clone https://github.com/dhirajparihar/shadow-vault.git
28
+ cd shadow-vault
29
+
30
+ # Create and activate a virtual environment
31
+ python -m venv .venv
32
+
33
+ # On Linux/macOS:
34
+ source .venv/bin/activate
35
+ # On Windows PowerShell:
36
+ .venv\Scripts\Activate.ps1
37
+ ```
38
+
39
+ ### 2. Install Development Dependencies
40
+
41
+ Install the package in editable mode with all development, testing, and framework integration dependencies:
42
+
43
+ ```bash
44
+ pip install -e ".[all]"
45
+ ```
46
+
47
+ ### 3. Run the Test Suite & Benchmarks
48
+
49
+ Make sure all tests pass before making any changes:
50
+
51
+ ```bash
52
+ # Run full pytest suite
53
+ pytest -v
54
+
55
+ # Run the CLI interactive demo
56
+ python examples/demo_agent_roundtrip.py
57
+
58
+ # Run the latency and memory benchmark suite
59
+ python benchmarks/run_benchmarks.py
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Adding a New Regional PII Detector in Under 20 Lines
65
+
66
+ Shadow Vault is designed to be easily extensible. You can add a new regional identity detector (such as **Brazilian CPF**, **UK National Insurance Number**, or **EU IBAN**) in under 20 lines of code.
67
+
68
+ Here is the complete step-by-step walkthrough.
69
+
70
+ ### The 4-Step Process
71
+
72
+ 1. **Register the Entity Type** in `shadow_vault/detectors.py`:
73
+ Add your new type to `EntityType` enum:
74
+ ```python
75
+ class EntityType(str, Enum):
76
+ ...
77
+ CPF = "CPF"
78
+ ```
79
+
80
+ 2. **Add Checksum & Regex Detection** in `shadow_vault/detectors.py`:
81
+ Write the mathematical check function and detector.
82
+
83
+ 3. **Add Format-Preserving Synthesis** in `shadow_vault/synthesizer.py`:
84
+ Add a branch in `synthesize_token()` so synthetic tokens mirror the real regional format.
85
+
86
+ 4. **Register in `detect_entities()`** and **Write Tests**:
87
+ Call your detector in `detect_entities()` and add test cases in `tests/test_detectors.py`.
88
+
89
+ ---
90
+
91
+ ### Complete Example: Brazilian CPF Detector (< 20 Lines)
92
+
93
+ Here is the exact code needed to implement Brazilian CPF (Cadastro de Pessoas Físicas) with official Modulo 11 check-digit verification:
94
+
95
+ ```python
96
+ # In shadow_vault/detectors.py (~18 lines of code)
97
+ import re
98
+
99
+ CPF_REGEX = re.compile(r"\b(?:\d{3}\.\d{3}\.\d{3}-\d{2}|\d{11})\b")
100
+
101
+ def is_cpf_valid(cpf_str: str) -> bool:
102
+ digits = [int(d) for d in re.sub(r"\D", "", cpf_str)]
103
+ if len(digits) != 11 or len(set(digits)) == 1:
104
+ return False
105
+ for i in (9, 10):
106
+ val = sum(digits[j] * ((i + 1) - j) for j in range(i)) % 11
107
+ check = 0 if val < 2 else 11 - val
108
+ if digits[i] != check:
109
+ return False
110
+ return True
111
+
112
+ def detect_cpf(text: str) -> list[PIIEntity]:
113
+ return [
114
+ PIIEntity(EntityType.CPF, m.group(0), m.start(), m.end())
115
+ for m in CPF_REGEX.finditer(text)
116
+ if is_cpf_valid(m.group(0))
117
+ ]
118
+ ```
119
+
120
+ ### Adding Format-Preserving Synthesis (~4 lines)
121
+
122
+ In `shadow_vault/synthesizer.py`, add your format pattern:
123
+
124
+ ```python
125
+ elif entity_type == EntityType.CPF:
126
+ # Produce format-preserving synthetic CPF with valid Modulo 11 check digits:
127
+ # e.g., "999.888.{short_hash[:3]}-{check_digits}"
128
+ return f"999.888.{int(short_hash[:3], 16) % 900 + 100:03d}-00"
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Unit Testing Requirements
134
+
135
+ Every new detector **must** include:
136
+ 1. **Valid Test Cases**: Known-valid numbers/identifiers with formatting variations (e.g., formatted with dots/dashes vs. unformatted raw digits).
137
+ 2. **Invalid Checksum Rejection**: Test that numbers with mutated check digits are rejected.
138
+ 3. **Repeated Digit Rejection**: Many national IDs reject sequences like `111.111.111-11`.
139
+ 4. **False Positive Guard**: Verify that unrelated numbers (order IDs, tracking numbers) are not detected.
140
+ 5. **Round-Trip Test**: Verify `dehydrate()` -> `hydrate()` preserves the original value.
141
+
142
+ Run tests:
143
+ ```bash
144
+ pytest tests/test_detectors.py -v
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Pull Request Guidelines
150
+
151
+ 1. **Branch Naming**: Use descriptive branch names:
152
+ - `feature/add-brazilian-cpf-detector`
153
+ - `feature/add-uk-nino-detector`
154
+ - `fix/phone-regex-edge-case`
155
+ 2. **Commit Messages**: Write clear, descriptive commit messages:
156
+ - `feat(detectors): add Brazilian CPF detector with Modulo 11 validation`
157
+ - `test(detectors): add test suite for UK National Insurance Number`
158
+ 3. **CI Passing**: Ensure GitHub Actions CI passes cleanly across Python 3.10, 3.11, 3.12, and 3.13.
159
+ 4. **Code Quality**:
160
+ - Zero lint errors or typing warnings.
161
+ - Preserves 100% backward compatibility.
162
+ - No heavy ML or external runtime dependencies added to core.
163
+
164
+ ---
165
+
166
+ ## Good First Issues
167
+
168
+ Looking for somewhere to start? Check our [Good First Issues](https://github.com/dhirajparihar/shadow-vault/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22):
169
+ - **Brazilian CPF Detector** with Modulo 11 Checksum
170
+ - **UK National Insurance Number (NINO)** Detector
171
+ - **EU / International IBAN** Detector with ISO 7064 MOD-97 Check
172
+ - **Australian Tax File Number (TFN)** with Modulo 11 Check
173
+ - **Canadian Social Insurance Number (SIN)** with Luhn Check
174
+
175
+ Thank you for helping keep AI agent applications private, secure, and compliant across the globe!
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shadow Vault Contributors
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.