keyctl2 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.
@@ -0,0 +1,3 @@
1
+ # SPDX-License-Identifier: 0BSD
2
+
3
+ * @Sudo-Ivan
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ cooldown:
8
+ default-days: 7
9
+ open-pull-requests-limit: 5
10
+ - package-ecosystem: uv
11
+ directory: /
12
+ schedule:
13
+ interval: weekly
14
+ cooldown:
15
+ default-days: 7
16
+ open-pull-requests-limit: 5
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "9 7 * * 1"
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
17
+ with:
18
+ egress-policy: audit
19
+
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ persist-credentials: false
23
+
24
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
25
+ with:
26
+ version: "0.11.32"
27
+ enable-cache: true
28
+
29
+ - run: uv sync --locked
30
+
31
+ - run: uv run ruff check .
32
+ - run: uv run ruff format --check .
33
+ - run: uv run bandit -r src/
34
+ - run: uv run mypy
35
+ - run: uvx ty check src tests
36
+
37
+ test:
38
+ runs-on: ubuntu-latest
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
43
+ steps:
44
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
45
+ with:
46
+ egress-policy: audit
47
+
48
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
49
+ with:
50
+ persist-credentials: false
51
+
52
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
53
+ with:
54
+ version: "0.11.32"
55
+ enable-cache: true
56
+
57
+ - run: uv python install ${{ matrix.python }}
58
+ - run: uv sync --locked --python ${{ matrix.python }}
59
+ - run: uv run pytest --cov
@@ -0,0 +1,35 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "17 3 * * 4"
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ analyze:
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ actions: read
17
+ contents: read
18
+ security-events: write
19
+ steps:
20
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
21
+ with:
22
+ egress-policy: audit
23
+
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
29
+ with:
30
+ languages: python
31
+ queries: security-and-quality
32
+
33
+ - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
34
+ with:
35
+ category: "/language:python"
@@ -0,0 +1,22 @@
1
+ name: Dependency Review
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions: {}
7
+
8
+ jobs:
9
+ dependency-review:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ steps:
14
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
15
+ with:
16
+ egress-policy: audit
17
+
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
@@ -0,0 +1,63 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions: {}
8
+
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ environment:
13
+ name: pypi
14
+ url: https://pypi.org/p/keyctl2
15
+ permissions:
16
+ contents: write
17
+ id-token: write
18
+ attestations: write
19
+ steps:
20
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
21
+ with:
22
+ egress-policy: audit
23
+
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
29
+ with:
30
+ version: "0.11.32"
31
+
32
+ - run: uv sync --locked
33
+
34
+ - name: Verify tag matches package version
35
+ run: |
36
+ tag="${GITHUB_REF_NAME#v}"
37
+ version="$(uv run python -c 'import keyctl2; print(keyctl2.__version__)')"
38
+ if [ "$tag" != "$version" ]; then
39
+ echo "tag $tag does not match package version $version" >&2
40
+ exit 1
41
+ fi
42
+
43
+ - run: uv build
44
+
45
+ - name: Attest build provenance
46
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
47
+ with:
48
+ subject-path: dist/*
49
+
50
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
51
+ with:
52
+ attestations: true
53
+
54
+ - name: Create GitHub release
55
+ env:
56
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57
+ run: |
58
+ gh release create "${GITHUB_REF_NAME}" \
59
+ --repo "${GITHUB_REPOSITORY}" \
60
+ --verify-tag \
61
+ --title "${GITHUB_REF_NAME}" \
62
+ --generate-notes \
63
+ dist/*
@@ -0,0 +1,42 @@
1
+ name: OpenSSF Scorecard
2
+
3
+ on:
4
+ branch_protection_rule:
5
+ push:
6
+ branches: [master]
7
+ workflow_dispatch:
8
+ schedule:
9
+ - cron: "42 5 * * 6"
10
+
11
+ permissions: read-all
12
+
13
+ jobs:
14
+ analysis:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ security-events: write
18
+ id-token: write
19
+ steps:
20
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
21
+ with:
22
+ egress-policy: audit
23
+
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
29
+ with:
30
+ results_file: results.sarif
31
+ results_format: sarif
32
+ publish_results: true
33
+
34
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
35
+ with:
36
+ name: SARIF file
37
+ path: results.sarif
38
+ retention-days: 5
39
+
40
+ - uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
41
+ with:
42
+ sarif_file: results.sarif
@@ -0,0 +1,28 @@
1
+ name: zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ paths: [.github/**]
7
+ pull_request:
8
+ paths: [.github/**]
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ zizmor:
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ security-events: write
17
+ steps:
18
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
19
+ with:
20
+ egress-policy: audit
21
+
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
27
+ with:
28
+ advanced-security: false
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .mypy_cache/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage*
11
+ htmlcov/
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - Unreleased
4
+
5
+ Initial release.
@@ -0,0 +1,19 @@
1
+ # Contributing
2
+
3
+ Report bugs through GitHub issues. Security reports go to
4
+ security@quad4.io (see SECURITY.md).
5
+
6
+ ## Setup
7
+
8
+ uv sync
9
+ make check
10
+
11
+ `make check` runs the full local gate: ruff lint and format, bandit,
12
+ mypy strict, ty, and pytest with coverage.
13
+
14
+ ## Conventions
15
+
16
+ - No runtime dependencies unless the project genuinely needs one.
17
+ - Source files start with the SPDX license identifier.
18
+ - Public API changes need tests.
19
+ - Commit messages: short, imperative, formal.
keyctl2-0.1.0/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2026 Quad4
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose
4
+ with or without fee is hereby granted.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
8
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12
+ PERFORMANCE OF THIS SOFTWARE.
keyctl2-0.1.0/Makefile ADDED
@@ -0,0 +1,16 @@
1
+ .PHONY: check lint test build
2
+
3
+ check: lint test
4
+
5
+ lint:
6
+ uv run ruff check .
7
+ uv run ruff format --check .
8
+ uv run bandit -r src/
9
+ uv run mypy
10
+ uvx ty check src tests
11
+
12
+ test:
13
+ uv run pytest --cov --cov-report=term-missing
14
+
15
+ build:
16
+ uv build
keyctl2-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.5
2
+ Name: keyctl2
3
+ Version: 0.1.0
4
+ Summary: Python bindings for the Linux kernel keyring
5
+ Project-URL: Homepage, https://quad4.io
6
+ Project-URL: Repository, https://github.com/Quad4-Software/keyctl2
7
+ Project-URL: Issues, https://github.com/Quad4-Software/keyctl2/issues
8
+ Project-URL: Changelog, https://github.com/Quad4-Software/keyctl2/blob/master/CHANGELOG.md
9
+ Author: Quad4
10
+ License-Expression: 0BSD
11
+ License-File: LICENSE
12
+ Keywords: keyctl,keyring,linux,secrets,security
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Security
24
+ Classifier: Topic :: System :: Operating System Kernels :: Linux
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+
29
+ # keyctl2
30
+
31
+ [![CI](https://github.com/Quad4-Software/keyctl2/actions/workflows/ci.yml/badge.svg)](https://github.com/Quad4-Software/keyctl2/actions/workflows/ci.yml)
32
+ [![CodeQL](https://github.com/Quad4-Software/keyctl2/actions/workflows/codeql.yml/badge.svg)](https://github.com/Quad4-Software/keyctl2/actions/workflows/codeql.yml)
33
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Quad4-Software/keyctl2/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Quad4-Software/keyctl2)
34
+ [![PyPI](https://img.shields.io/pypi/v/keyctl2.svg)](https://pypi.org/project/keyctl2/)
35
+ [![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue)](LICENSE)
36
+
37
+ Dependency-free Python bindings for the Linux kernel keyring: add,
38
+ search, read, describe, link and unlink keys in session and process
39
+ keyrings, without shelling out to keyctl(1).
40
+
41
+ Requires Python 3.10+ and Linux. No runtime dependencies: the bindings
42
+ call add_key(2), request_key(2) and keyctl(2) through ctypes.
43
+
44
+ ## Install
45
+
46
+ pip install keyctl2
47
+
48
+ ## Example
49
+
50
+ import keyctl2
51
+
52
+ # Add a user key to the session keyring and read it back.
53
+ serial = keyctl2.add_key("user", "my-secret", b"hunter2")
54
+ key = keyctl2.Key(serial)
55
+ assert key.read() == b"hunter2"
56
+
57
+ # Search the session keyring for it later.
58
+ assert keyctl2.search("session", "user", "my-secret") == serial
59
+
60
+ # Inspect it and clean up.
61
+ print(key.describe()) # KeyDescription(type='user', ...)
62
+ key.unlink("session")
63
+
64
+ ## Development
65
+
66
+ uv sync --group dev
67
+ make check
68
+
69
+ License: 0BSD. Quad4 Software, https://quad4.io
@@ -0,0 +1,41 @@
1
+ # keyctl2
2
+
3
+ [![CI](https://github.com/Quad4-Software/keyctl2/actions/workflows/ci.yml/badge.svg)](https://github.com/Quad4-Software/keyctl2/actions/workflows/ci.yml)
4
+ [![CodeQL](https://github.com/Quad4-Software/keyctl2/actions/workflows/codeql.yml/badge.svg)](https://github.com/Quad4-Software/keyctl2/actions/workflows/codeql.yml)
5
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Quad4-Software/keyctl2/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Quad4-Software/keyctl2)
6
+ [![PyPI](https://img.shields.io/pypi/v/keyctl2.svg)](https://pypi.org/project/keyctl2/)
7
+ [![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue)](LICENSE)
8
+
9
+ Dependency-free Python bindings for the Linux kernel keyring: add,
10
+ search, read, describe, link and unlink keys in session and process
11
+ keyrings, without shelling out to keyctl(1).
12
+
13
+ Requires Python 3.10+ and Linux. No runtime dependencies: the bindings
14
+ call add_key(2), request_key(2) and keyctl(2) through ctypes.
15
+
16
+ ## Install
17
+
18
+ pip install keyctl2
19
+
20
+ ## Example
21
+
22
+ import keyctl2
23
+
24
+ # Add a user key to the session keyring and read it back.
25
+ serial = keyctl2.add_key("user", "my-secret", b"hunter2")
26
+ key = keyctl2.Key(serial)
27
+ assert key.read() == b"hunter2"
28
+
29
+ # Search the session keyring for it later.
30
+ assert keyctl2.search("session", "user", "my-secret") == serial
31
+
32
+ # Inspect it and clean up.
33
+ print(key.describe()) # KeyDescription(type='user', ...)
34
+ key.unlink("session")
35
+
36
+ ## Development
37
+
38
+ uv sync --group dev
39
+ make check
40
+
41
+ License: 0BSD. Quad4 Software, https://quad4.io
@@ -0,0 +1,6 @@
1
+ # Security policy
2
+
3
+ Only the latest release is supported.
4
+
5
+ Report vulnerabilities to security@quad4.io. Do not open public issues for
6
+ security reports.
@@ -0,0 +1,114 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "keyctl2"
7
+ dynamic = ["version"]
8
+ description = "Python bindings for the Linux kernel keyring"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "0BSD"
12
+ authors = [{ name = "Quad4" }]
13
+ keywords = ["keyctl", "keyring", "linux", "security", "secrets"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: BSD License",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Security",
26
+ "Topic :: System :: Operating System Kernels :: Linux",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = []
30
+
31
+ [project.urls]
32
+ Homepage = "https://quad4.io"
33
+ Repository = "https://github.com/Quad4-Software/keyctl2"
34
+ Issues = "https://github.com/Quad4-Software/keyctl2/issues"
35
+ Changelog = "https://github.com/Quad4-Software/keyctl2/blob/master/CHANGELOG.md"
36
+
37
+ [tool.hatch.version]
38
+ path = "src/keyctl2/__init__.py"
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest>=9.1.1",
43
+ "pytest-cov>=7.1.0",
44
+ "ruff>=0.16.8",
45
+ "bandit>=1.9.4",
46
+ "mypy>=2.3.1",
47
+ ]
48
+
49
+ [tool.ruff]
50
+ target-version = "py310"
51
+ src = ["src", "tests"]
52
+
53
+ [tool.ruff.lint]
54
+ select = [
55
+ "E", "W", # pycodestyle
56
+ "F", # pyflakes
57
+ "I", # isort
58
+ "N", # pep8-naming
59
+ "UP", # pyupgrade
60
+ "YTT", # sys.version misuse
61
+ "FA", # future annotations
62
+ "A", # builtins shadowing
63
+ "B", # flake8-bugbear
64
+ "BLE", # blind except
65
+ "C4", # comprehensions
66
+ "C90", # mccabe complexity
67
+ "DTZ", # timezone-aware datetimes
68
+ "ERA", # commented-out code
69
+ "FURB", # refurb
70
+ "G", # logging format
71
+ "ICN", # import conventions
72
+ "ISC", # implicit string concat
73
+ "LOG", # logging
74
+ "PERF", # perflint
75
+ "PGH", # pygrep hooks
76
+ "PIE", # misc lints
77
+ "PT", # pytest style
78
+ "PTH", # pathlib
79
+ "RET", # return
80
+ "RSE", # raise/return
81
+ "S", # bandit rules
82
+ "SIM", # simplify
83
+ "SLOT", # __slots__
84
+ "T20", # print
85
+ "TRY", # exception handling
86
+ "RUF", # ruff-specific
87
+ ]
88
+ ignore = [
89
+ "ISC001", # single-line implicit concat conflicts with the formatter
90
+ "TRY003", # long messages in raise calls are fine
91
+ ]
92
+
93
+ [tool.ruff.lint.per-file-ignores]
94
+ "tests/*" = ["S101", "S603"]
95
+
96
+ [tool.mypy]
97
+ strict = true
98
+ files = ["src", "tests"]
99
+
100
+ [tool.bandit]
101
+ exclude_dirs = ["tests"]
102
+
103
+ [tool.coverage.run]
104
+ branch = true
105
+ source = ["src/keyctl2"]
106
+
107
+ [tool.coverage.report]
108
+ show_missing = true
109
+ fail_under = 50
110
+
111
+ [tool.pytest.ini_options]
112
+ testpaths = ["tests"]
113
+ xfail_strict = true
114
+ filterwarnings = ["error"]
@@ -0,0 +1,62 @@
1
+ # SPDX-License-Identifier: 0BSD
2
+ """Python bindings for the Linux kernel key retention service.
3
+
4
+ Create and search keys and keyrings through add_key(2), request_key(2)
5
+ and keyctl(2) via ctypes; there are no runtime dependencies.
6
+
7
+ Kernel references: keyctl(2), add_key(2), request_key(2), keyrings(7).
8
+ """
9
+
10
+ from .errors import KeyctlError, UnsupportedError
11
+ from .flags import (
12
+ KeyctlOp,
13
+ KeyPerm,
14
+ KeySpec,
15
+ KeyType,
16
+ MoveFlag,
17
+ ReqKeyDefault,
18
+ )
19
+ from .keys import (
20
+ Key,
21
+ KeyDescription,
22
+ add_key,
23
+ capabilities,
24
+ clear,
25
+ get_keyring_id,
26
+ get_persistent,
27
+ join_session_keyring,
28
+ keyring,
29
+ request_key,
30
+ restrict,
31
+ search,
32
+ session_to_parent,
33
+ set_reqkey_keyring,
34
+ )
35
+
36
+ __version__ = "0.1.0"
37
+
38
+ __all__ = [
39
+ "Key",
40
+ "KeyDescription",
41
+ "KeyPerm",
42
+ "KeySpec",
43
+ "KeyType",
44
+ "KeyctlError",
45
+ "KeyctlOp",
46
+ "MoveFlag",
47
+ "ReqKeyDefault",
48
+ "UnsupportedError",
49
+ "__version__",
50
+ "add_key",
51
+ "capabilities",
52
+ "clear",
53
+ "get_keyring_id",
54
+ "get_persistent",
55
+ "join_session_keyring",
56
+ "keyring",
57
+ "request_key",
58
+ "restrict",
59
+ "search",
60
+ "session_to_parent",
61
+ "set_reqkey_keyring",
62
+ ]