kiff-scan 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 (39) hide show
  1. kiff_scan-0.1.0/.github/workflows/ci.yml +75 -0
  2. kiff_scan-0.1.0/.github/workflows/release.yml +142 -0
  3. kiff_scan-0.1.0/.github/workflows/verify-claims.yml +90 -0
  4. kiff_scan-0.1.0/.gitignore +15 -0
  5. kiff_scan-0.1.0/.kiff-scan.json +7 -0
  6. kiff_scan-0.1.0/CHANGELOG.md +49 -0
  7. kiff_scan-0.1.0/CONTRIBUTING.md +87 -0
  8. kiff_scan-0.1.0/LICENSE +21 -0
  9. kiff_scan-0.1.0/PKG-INFO +355 -0
  10. kiff_scan-0.1.0/README.md +307 -0
  11. kiff_scan-0.1.0/SECURITY.md +44 -0
  12. kiff_scan-0.1.0/action.yml +146 -0
  13. kiff_scan-0.1.0/docs/COVERAGE.md +161 -0
  14. kiff_scan-0.1.0/docs/RELEASING.md +96 -0
  15. kiff_scan-0.1.0/docs/THREAT_MODEL.md +92 -0
  16. kiff_scan-0.1.0/pyproject.toml +72 -0
  17. kiff_scan-0.1.0/src/kiff_scan/__init__.py +49 -0
  18. kiff_scan-0.1.0/src/kiff_scan/__main__.py +10 -0
  19. kiff_scan-0.1.0/src/kiff_scan/cli.py +211 -0
  20. kiff_scan-0.1.0/src/kiff_scan/config.py +117 -0
  21. kiff_scan-0.1.0/src/kiff_scan/detectors/__init__.py +10 -0
  22. kiff_scan-0.1.0/src/kiff_scan/detectors/decisions.py +234 -0
  23. kiff_scan-0.1.0/src/kiff_scan/detectors/reachability.py +146 -0
  24. kiff_scan-0.1.0/src/kiff_scan/detectors/sinks.py +271 -0
  25. kiff_scan-0.1.0/src/kiff_scan/engine.py +184 -0
  26. kiff_scan-0.1.0/src/kiff_scan/model.py +155 -0
  27. kiff_scan-0.1.0/src/kiff_scan/report/__init__.py +6 -0
  28. kiff_scan-0.1.0/src/kiff_scan/report/json_out.py +118 -0
  29. kiff_scan-0.1.0/src/kiff_scan/report/pretty.py +223 -0
  30. kiff_scan-0.1.0/src/kiff_scan/report/sarif.py +137 -0
  31. kiff_scan-0.1.0/src/kiff_scan/taxonomy.py +140 -0
  32. kiff_scan-0.1.0/tests/fixtures/hooked_agent.py +46 -0
  33. kiff_scan-0.1.0/tests/fixtures/mixed_governance.py +70 -0
  34. kiff_scan-0.1.0/tests/fixtures/ungoverned_ops.py +83 -0
  35. kiff_scan-0.1.0/tests/test_cli.py +166 -0
  36. kiff_scan-0.1.0/tests/test_engine.py +281 -0
  37. kiff_scan-0.1.0/tests/test_interprocedural.py +210 -0
  38. kiff_scan-0.1.0/tests/test_no_egress.py +180 -0
  39. kiff_scan-0.1.0/tests/test_zero_deps.py +83 -0
@@ -0,0 +1,75 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 6 * * 1"
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-latest, windows-latest]
20
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ - run: python -m pip install --upgrade pip
27
+ - run: python -m pip install -e ".[dev]"
28
+ - run: python -m pytest -v
29
+
30
+ no-network:
31
+ name: test suite passes with no network access
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: actions/setup-python@v5
36
+ with:
37
+ python-version: "3.12"
38
+ - run: python -m pip install -e ".[dev]"
39
+ - name: Run the suite with no network access
40
+ # Isolate only the test process in a fresh network namespace with nothing
41
+ # but loopback. Dropping traffic globally (iptables -P OUTPUT DROP) would
42
+ # also sever the runner's own connection to GitHub and hang the job.
43
+ #
44
+ # The interpreter is resolved *before* sudo: sudo resets PATH, so
45
+ # resolving it inside would pick /usr/bin/python, which has no pytest.
46
+ run: |
47
+ set -euo pipefail
48
+ PY="$(command -v python)"
49
+ echo "using interpreter: $PY"
50
+ sudo unshare --net -- sh -c 'ip link set lo up; exec "$1" -m pytest -q' _ "$PY"
51
+
52
+ lint:
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+ - uses: actions/setup-python@v5
57
+ with:
58
+ python-version: "3.12"
59
+ - run: python -m pip install ruff==0.6.9
60
+ - run: ruff check src tests
61
+ - run: ruff format --check src tests
62
+
63
+ self-scan:
64
+ name: kiff-scan scans itself
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: actions/setup-python@v5
69
+ with:
70
+ python-version: "3.12"
71
+ - run: python -m pip install -e .
72
+ # Scanning our own source must find nothing: kiff-scan exposes no agent
73
+ # tools. The fixtures are excluded by .kiff-scan.json because they contain
74
+ # deliberately unguarded code.
75
+ - run: kiff-scan scan . --fail-on low
@@ -0,0 +1,142 @@
1
+ name: release
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC). There is no API token stored
4
+ # in this repository: PyPI verifies a short-lived token minted by GitHub for this
5
+ # exact workflow file, in this exact repository and environment. Nothing to leak,
6
+ # nothing to rotate.
7
+ #
8
+ # One-time setup on PyPI, before the first release:
9
+ # PyPI -> Your projects (or "Publishing" for a new project) -> Add a pending
10
+ # publisher, with:
11
+ # Owner: kiff
12
+ # Repository: kiff-scan
13
+ # Workflow name: release.yml
14
+ # Environment: pypi
15
+ #
16
+ # Then push a tag to publish:
17
+ # git tag v0.1.0 && git push origin v0.1.0
18
+
19
+ on:
20
+ push:
21
+ tags: ["v*"]
22
+ workflow_dispatch:
23
+ inputs:
24
+ target:
25
+ description: "Where to publish"
26
+ required: true
27
+ default: testpypi
28
+ type: choice
29
+ options: [testpypi, pypi]
30
+
31
+ permissions:
32
+ contents: read
33
+
34
+ jobs:
35
+ # Never publish something that does not pass its own gates. The security
36
+ # claims in the README are enforced by these tests, so a release that skipped
37
+ # them would be a release of unverified claims.
38
+ verify:
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.12"
45
+ - run: python -m pip install -e ".[dev]"
46
+ - name: Tests
47
+ run: python -m pytest -v
48
+ - name: Security gates
49
+ run: python -m pytest tests/test_no_egress.py tests/test_zero_deps.py -v
50
+ - name: Self-scan must be clean
51
+ run: kiff-scan scan . --fail-on low
52
+
53
+ build:
54
+ needs: verify
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: "3.12"
61
+ - run: python -m pip install build
62
+ - run: python -m build
63
+ - name: Tag must match the packaged version
64
+ if: startsWith(github.ref, 'refs/tags/v')
65
+ run: |
66
+ set -euo pipefail
67
+ tag="${GITHUB_REF_NAME#v}"
68
+ pkg="$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
69
+ if [ "$tag" != "$pkg" ]; then
70
+ echo "tag v$tag does not match pyproject version $pkg"
71
+ exit 1
72
+ fi
73
+ echo "tag and version agree: $pkg"
74
+ - name: Wheel must declare no runtime dependencies
75
+ run: |
76
+ set -euo pipefail
77
+ python -m venv /tmp/clean
78
+ /tmp/clean/bin/python -m pip install --quiet dist/*.whl
79
+ extra=$(/tmp/clean/bin/python -m pip list --format=freeze \
80
+ | grep -v '^kiff-scan==' | grep -v '^pip==' \
81
+ | grep -v '^setuptools==' | grep -v '^wheel==' || true)
82
+ if [ -n "$extra" ]; then
83
+ echo "the wheel pulled in dependencies: $extra"
84
+ exit 1
85
+ fi
86
+ /tmp/clean/bin/kiff-scan --version
87
+ - uses: actions/upload-artifact@v4
88
+ with:
89
+ name: dist
90
+ path: dist/
91
+
92
+ publish-testpypi:
93
+ needs: build
94
+ if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
95
+ runs-on: ubuntu-latest
96
+ environment: testpypi
97
+ permissions:
98
+ id-token: write # required for Trusted Publishing
99
+ steps:
100
+ - uses: actions/download-artifact@v4
101
+ with:
102
+ name: dist
103
+ path: dist/
104
+ - uses: pypa/gh-action-pypi-publish@release/v1
105
+ with:
106
+ repository-url: https://test.pypi.org/legacy/
107
+
108
+ publish-pypi:
109
+ needs: build
110
+ if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
111
+ runs-on: ubuntu-latest
112
+ environment: pypi
113
+ permissions:
114
+ id-token: write # required for Trusted Publishing
115
+ steps:
116
+ - uses: actions/download-artifact@v4
117
+ with:
118
+ name: dist
119
+ path: dist/
120
+ # Publishes with PEP 740 provenance attestations by default, so a user can
121
+ # verify the artifact was built from this repository at this commit.
122
+ - uses: pypa/gh-action-pypi-publish@release/v1
123
+
124
+ github-release:
125
+ needs: publish-pypi
126
+ if: startsWith(github.ref, 'refs/tags/v')
127
+ runs-on: ubuntu-latest
128
+ permissions:
129
+ contents: write
130
+ steps:
131
+ - uses: actions/checkout@v4
132
+ - uses: actions/download-artifact@v4
133
+ with:
134
+ name: dist
135
+ path: dist/
136
+ - name: Create the GitHub release
137
+ env:
138
+ GH_TOKEN: ${{ github.token }}
139
+ run: |
140
+ gh release create "$GITHUB_REF_NAME" dist/* \
141
+ --title "$GITHUB_REF_NAME" \
142
+ --notes "See [CHANGELOG.md](./CHANGELOG.md) for what changed."
@@ -0,0 +1,90 @@
1
+ name: verify-claims
2
+
3
+ # Every factual claim the README makes about this package is checked here. If a
4
+ # claim stops being true, this workflow goes red before a human notices.
5
+ #
6
+ # The claims under test:
7
+ # 1. Zero runtime dependencies.
8
+ # 2. No network code anywhere in the package (no telemetry, no upload).
9
+ # 3. The scanner never executes the code it analyses.
10
+ # 4. Documented exit codes behave as documented.
11
+ # 5. The version in the README install line resolves.
12
+
13
+ on:
14
+ push:
15
+ branches: [main]
16
+ pull_request:
17
+ schedule:
18
+ - cron: "0 6 * * *"
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ verify:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.12"
31
+ - run: python -m pip install -e ".[dev]"
32
+
33
+ - name: Claim - zero runtime dependencies
34
+ run: python -m pytest tests/test_zero_deps.py -v
35
+
36
+ - name: Claim - no egress, no telemetry, no code execution
37
+ run: python -m pytest tests/test_no_egress.py -v
38
+
39
+ - name: Claim - an installed wheel pulls in nothing
40
+ run: |
41
+ set -euo pipefail
42
+ # Build a real wheel. `pip download .` yields an sdist, not a wheel.
43
+ python -m pip install --quiet build
44
+ rm -rf /tmp/whl
45
+ python -m build --wheel --outdir /tmp/whl
46
+ python -m venv /tmp/clean
47
+ /tmp/clean/bin/python -m pip install --quiet /tmp/whl/*.whl
48
+ installed=$(/tmp/clean/bin/python -m pip list --format=freeze | grep -v '^kiff-scan==' | grep -v '^pip==' | grep -v '^setuptools==' | grep -v '^wheel==' || true)
49
+ if [ -n "$installed" ]; then
50
+ echo "installing kiff-scan pulled in: $installed"
51
+ exit 1
52
+ fi
53
+ /tmp/clean/bin/kiff-scan --version
54
+ echo "clean install contains only kiff-scan"
55
+
56
+ - name: Claim - documented exit codes
57
+ run: |
58
+ # errexit must be OFF here: these commands are *expected* to exit
59
+ # non-zero, and that exit code is the thing under test.
60
+ set +e
61
+
62
+ kiff-scan scan tests/fixtures/ungoverned_ops.py > /dev/null 2>&1
63
+ [ $? -eq 1 ] || { echo "expected exit 1 on findings"; exit 1; }
64
+
65
+ kiff-scan scan tests/fixtures/hooked_agent.py > /dev/null 2>&1
66
+ [ $? -eq 0 ] || { echo "expected exit 0 when all findings are governed"; exit 1; }
67
+
68
+ kiff-scan scan /nonexistent > /dev/null 2>&1
69
+ [ $? -eq 2 ] || { echo "expected exit 2 on usage error"; exit 1; }
70
+
71
+ kiff-scan scan tests/fixtures/ungoverned_ops.py --fail-on none > /dev/null 2>&1
72
+ [ $? -eq 0 ] || { echo "expected exit 0 with --fail-on none"; exit 1; }
73
+
74
+ echo "all documented exit codes behave as documented"
75
+
76
+ - name: Claim - SARIF output is valid JSON with the declared schema
77
+ run: |
78
+ set -euo pipefail
79
+ kiff-scan scan tests/fixtures --format sarif --output /tmp/out.sarif --fail-on none
80
+ python - <<'PY'
81
+ import json
82
+ with open("/tmp/out.sarif", encoding="utf-8") as fh:
83
+ doc = json.load(fh)
84
+ assert doc["version"] == "2.1.0", doc["version"]
85
+ run = doc["runs"][0]
86
+ declared = {r["id"] for r in run["tool"]["driver"]["rules"]}
87
+ for result in run["results"]:
88
+ assert result["ruleId"] in declared, result["ruleId"]
89
+ print(f"SARIF valid: {len(run['results'])} results, {len(declared)} rules")
90
+ PY
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ htmlcov/
12
+ *.sarif
13
+ report.html
14
+ report.json
15
+ .DS_Store
@@ -0,0 +1,7 @@
1
+ {
2
+ "_comment": "kiff-scan's own config. tests/fixtures/ contains deliberately unguarded sample code used as scanner input, so it is excluded from a scan of this repository. Without this, kiff-scan would report findings against its own test data.",
3
+ "exclude": [
4
+ "tests/fixtures/*",
5
+ "tests/fixtures/**"
6
+ ]
7
+ }
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.1.0] — 2026-09-01
7
+
8
+ First release.
9
+
10
+ ### Added
11
+
12
+ - Static analysis of Python source for agent-reachable consequential actions.
13
+ A finding requires all three of: reachable by a model, consequential, and no
14
+ decision found on the analysed path.
15
+ - Eight consequence categories, seven of them marked `state_dependent` — the
16
+ cases where an authorization check is necessary but not sufficient because the
17
+ action's safety depends on live state at the moment of execution.
18
+ - Per-function decision detection with named evidence levels (`decorator`,
19
+ `call_before_sink`, `module_hook`, `call_after_sink`, `none`), including
20
+ lexical precedence so a guard that runs *after* the consequential call is
21
+ reported rather than credited.
22
+ - Vendor-neutral guard vocabulary: roughly 30 common authorization and approval
23
+ function names recognised out of the box, extensible with `--guard` or config.
24
+ - Match confidence (`call` vs `declared`), with name-only inferences capped below
25
+ `high` severity so they cannot fail a build on their own.
26
+ - Bounded interprocedural analysis: calls to functions defined in the same module
27
+ are followed up to four hops, so a tool that delegates its destructive work to
28
+ a helper is still reported, with the chain named in the reason. Cycles
29
+ terminate, and guards inside the chain are credited so the added reach does not
30
+ create false positives.
31
+ - Reporters: terminal, JSON (`schema_version: 1`), SARIF 2.1.0, and Markdown.
32
+ - `explain FILE:LINE` for the analysed path behind one finding.
33
+ - `--fail-on {none,low,medium,high}`, defaulting to `medium` in the CLI and
34
+ `none` in the GitHub Action.
35
+ - Unsupported files are reported explicitly and never counted as clean.
36
+ - Configuration via `.kiff-scan.json`: custom guards, custom tool decorators,
37
+ exclude globs, and excluded directories.
38
+ - GitHub Action with a sticky pull request comment and SARIF upload.
39
+ - Security gates in CI: no network-capable imports, no telemetry, no socket
40
+ opened during a scan, the suite passing with outbound traffic blocked, no
41
+ execution of analysed code, read-only on disk, and zero runtime dependencies
42
+ verified against a clean install of the built wheel.
43
+
44
+ ### Notes
45
+
46
+ Python only in this release. TypeScript and Go files are reported as unsupported
47
+ rather than skipped silently. See [docs/COVERAGE.md](./docs/COVERAGE.md) for the
48
+ full list of what is and is not analysed, and the known limitations of decision
49
+ detection.
@@ -0,0 +1,87 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/kiff/kiff-scan
7
+ cd kiff-scan
8
+ python -m venv .venv && source .venv/bin/activate
9
+ pip install -e ".[dev]"
10
+ pytest
11
+ ```
12
+
13
+ There is no build step and no compilation. `pytest` should run in under a second.
14
+
15
+ ## The two rules that are not negotiable
16
+
17
+ **1. No runtime dependencies.** `pyproject.toml`'s `dependencies` list stays
18
+ empty. This is the property that lets a security team run kiff-scan against a
19
+ codebase that has adopted nothing, and it is enforced by
20
+ `tests/test_zero_deps.py`. If you believe a dependency is genuinely required,
21
+ open an issue first — it is a product decision, not a refactor.
22
+
23
+ **2. No network code.** No `socket`, `urllib`, `http`, `requests`, or anything
24
+ that can open a connection, anywhere in the package. Also no `subprocess` and no
25
+ `ctypes`. Enforced by `tests/test_no_egress.py`, which checks the import graph,
26
+ runs a scan with the socket constructor sabotaged, and verifies a scan writes
27
+ nothing.
28
+
29
+ Both are checked on every push. A pull request that trips either gate will fail
30
+ before review.
31
+
32
+ ## Adding a sink
33
+
34
+ Sinks live in `src/kiff_scan/detectors/sinks.py`.
35
+
36
+ 1. Add the call's final attribute to `SINK_CALLS`, mapped to a consequence
37
+ category. Match the method name, not the full qualified path, so that
38
+ `client.delete_thing()` and `boto3.client("x").delete_thing()` both resolve.
39
+ 2. Add a case to `tests/fixtures/` and assert it in `tests/test_engine.py`.
40
+ 3. If it needs a new category, add it to `src/kiff_scan/taxonomy.py` and decide
41
+ `state_dependent` deliberately — see below.
42
+
43
+ ## Adding a guard
44
+
45
+ Guards live in `src/kiff_scan/detectors/decisions.py`. Vendor neutrality is a
46
+ design commitment: a codebase's own `authorize()` must clear a finding exactly as
47
+ a KIFF decision does. Do not add a check that privileges KIFF over an equivalent
48
+ in-house guard.
49
+
50
+ ## Deciding `state_dependent`
51
+
52
+ Ask: *can this action be fully authorized, requested by a legitimate principal,
53
+ with valid inputs, and still be the wrong thing to do because of the state of the
54
+ world right now?*
55
+
56
+ If yes, it is state-dependent (a rollback to a bad revision, a failover to the
57
+ active region, a second refund on an unsettled order). If an authorization check
58
+ plus input validation genuinely closes it, it is not (arbitrary shell execution).
59
+
60
+ Getting this wrong in either direction misleads the reader, so justify it in the
61
+ `why` field, which is printed in the report.
62
+
63
+ ## Reporting a scanner mistake
64
+
65
+ A case where the scanner is wrong is the most valuable contribution. In scope:
66
+
67
+ - a supported-language sink it misses
68
+ - a recognised guard it fails to honour
69
+ - a finding on code with no model-controlled input on the path
70
+ - any case where it reports clean when it should not
71
+
72
+ Open an issue with a minimal reproducing file. Accepted cases become permanent
73
+ fixtures, credited by GitHub handle. Known misses stay listed even before they
74
+ are fixed; a challenge list that only shows fixed cases is a trophy cabinet.
75
+
76
+ ## Style
77
+
78
+ `ruff check src tests` and `ruff format --check src tests` must pass. Comments
79
+ should explain *why*, especially where a detector is deliberately conservative —
80
+ that reasoning is the part a future reader cannot reconstruct.
81
+
82
+ ## Tests
83
+
84
+ New behaviour needs a test. Fixtures under `tests/fixtures/` are scanner *input*
85
+ and must never be imported or collected; `pyproject.toml` ignores that directory
86
+ for collection, and the repository's own `.kiff-scan.json` excludes it from a
87
+ self-scan.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KIFF
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.