superencryptx 0.1.0__py3-none-any.whl

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,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
superencrypt/cli.py ADDED
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ from .crypto import Crypto
9
+ from .scanner import scan_repo, iter_repo_files
10
+ from .transform import encrypt_file, decrypt_file
11
+
12
+
13
+ DEFAULT_KEY_FILE = ".superencrypt.key"
14
+
15
+
16
+ def _load_key_from_args(args: argparse.Namespace) -> bytes:
17
+ if args.key:
18
+ return args.key.encode("utf-8")
19
+ if args.key_file:
20
+ return Path(args.key_file).read_bytes().strip()
21
+ raise SystemExit("Missing key: provide --key or --key-file")
22
+
23
+
24
+ def _write_key_file(path: Path, key: bytes) -> None:
25
+ path.write_bytes(key + b"\n")
26
+
27
+
28
+ def cmd_scan(args: argparse.Namespace) -> int:
29
+ root = Path(args.root).resolve()
30
+ findings = scan_repo(root)
31
+ if not findings:
32
+ print("No secrets found.")
33
+ return 0
34
+ for finding in findings:
35
+ rel = finding.path.relative_to(root)
36
+ key = finding.key or "secret"
37
+ print(f"{rel}:{finding.line_number} {key}={finding.value}")
38
+ print(f"\nFound {len(findings)} potential secrets.")
39
+ return 1
40
+
41
+
42
+ def cmd_encrypt(args: argparse.Namespace) -> int:
43
+ root = Path(args.root).resolve()
44
+ if args.key or args.key_file:
45
+ key = _load_key_from_args(args)
46
+ else:
47
+ key = Crypto.generate_key()
48
+ print(key.decode("utf-8"))
49
+ _write_key_file(Path(DEFAULT_KEY_FILE), key)
50
+ crypto = Crypto(key)
51
+
52
+ changed_files: List[Path] = []
53
+ for path in iter_repo_files(root):
54
+ result = encrypt_file(path, crypto)
55
+ if result.changed:
56
+ changed_files.append(result.path)
57
+ if changed_files:
58
+ print(f"Encrypted {len(changed_files)} files.")
59
+ else:
60
+ print("No changes made.")
61
+ return 0
62
+
63
+
64
+ def cmd_decrypt(args: argparse.Namespace) -> int:
65
+ root = Path(args.root).resolve()
66
+ key = _load_key_from_args(args)
67
+ crypto = Crypto(key)
68
+
69
+ changed_files: List[Path] = []
70
+ for path in iter_repo_files(root):
71
+ result = decrypt_file(path, crypto)
72
+ if result.changed:
73
+ changed_files.append(result.path)
74
+ if changed_files:
75
+ print(f"Decrypted {len(changed_files)} files.")
76
+ else:
77
+ print("No changes made.")
78
+ return 0
79
+
80
+
81
+ def build_parser() -> argparse.ArgumentParser:
82
+ parser = argparse.ArgumentParser(prog="superencrypt")
83
+ parser.add_argument("--root", default=".", help="Root directory to scan")
84
+
85
+ subparsers = parser.add_subparsers(dest="command", required=True)
86
+
87
+ scan_parser = subparsers.add_parser("scan", help="Scan repo for secrets")
88
+ scan_parser.set_defaults(func=cmd_scan)
89
+
90
+ encrypt_parser = subparsers.add_parser("encrypt", help="Encrypt secrets in-place")
91
+ encrypt_parser.add_argument("--key", help="Base64 key string")
92
+ encrypt_parser.add_argument("--key-file", help="Path to key file")
93
+ encrypt_parser.set_defaults(func=cmd_encrypt)
94
+
95
+ decrypt_parser = subparsers.add_parser("decrypt", help="Decrypt secrets in-place")
96
+ decrypt_parser.add_argument("--key", help="Base64 key string")
97
+ decrypt_parser.add_argument("--key-file", help="Path to key file")
98
+ decrypt_parser.set_defaults(func=cmd_decrypt)
99
+
100
+ return parser
101
+
102
+
103
+ def main() -> int:
104
+ parser = build_parser()
105
+ args = parser.parse_args()
106
+ return args.func(args)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ raise SystemExit(main())
superencrypt/crypto.py ADDED
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from cryptography.fernet import Fernet, InvalidToken
7
+
8
+
9
+ ENC_PREFIX = "ENC["
10
+ ENC_SUFFIX = "]"
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class EncryptionResult:
15
+ token: str
16
+
17
+
18
+ class CryptoError(RuntimeError):
19
+ pass
20
+
21
+
22
+ class Crypto:
23
+ def __init__(self, key: bytes):
24
+ self._fernet = Fernet(key)
25
+
26
+ @staticmethod
27
+ def generate_key() -> bytes:
28
+ return Fernet.generate_key()
29
+
30
+ def encrypt(self, plaintext: str) -> EncryptionResult:
31
+ token = self._fernet.encrypt(plaintext.encode("utf-8")).decode("utf-8")
32
+ return EncryptionResult(token=token)
33
+
34
+ def decrypt(self, token: str) -> str:
35
+ try:
36
+ return self._fernet.decrypt(token.encode("utf-8")).decode("utf-8")
37
+ except InvalidToken as exc:
38
+ raise CryptoError("Invalid decryption key or token") from exc
39
+
40
+
41
+ def is_encrypted_value(value: str) -> bool:
42
+ return value.startswith(ENC_PREFIX) and value.endswith(ENC_SUFFIX)
43
+
44
+
45
+ def wrap_encrypted(token: str) -> str:
46
+ return f"{ENC_PREFIX}{token}{ENC_SUFFIX}"
47
+
48
+
49
+ def unwrap_encrypted(value: str) -> Optional[str]:
50
+ if not is_encrypted_value(value):
51
+ return None
52
+ return value[len(ENC_PREFIX) : -len(ENC_SUFFIX)]
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Iterable, Iterator, List, Optional
8
+
9
+
10
+ SKIP_DIRS = {
11
+ ".git",
12
+ ".hg",
13
+ ".svn",
14
+ "node_modules",
15
+ "dist",
16
+ "build",
17
+ ".venv",
18
+ "venv",
19
+ "__pycache__",
20
+ }
21
+
22
+ SKIP_FILES = {
23
+ ".superencrypt.key",
24
+ }
25
+
26
+ ENV_FILE_PATTERNS = (
27
+ ".env",
28
+ ".env.",
29
+ ".envrc",
30
+ )
31
+
32
+ SENSITIVE_KEYWORDS = re.compile(
33
+ r"(?i)(password|passwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|db[_-]?user|database[_-]?user)"
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class Finding:
39
+ path: Path
40
+ line_number: int
41
+ key: Optional[str]
42
+ value: str
43
+
44
+
45
+ @dataclass
46
+ class SecretPattern:
47
+ name: str
48
+ regex: re.Pattern
49
+ group: int
50
+
51
+
52
+ SECRET_PATTERNS: List[SecretPattern] = [
53
+ SecretPattern(
54
+ name="aws_access_key_id",
55
+ regex=re.compile(r"\b(AKIA[0-9A-Z]{16})\b"),
56
+ group=1,
57
+ ),
58
+ SecretPattern(
59
+ name="aws_secret_access_key",
60
+ regex=re.compile(r"(?i)aws_secret_access_key\s*[:=]\s*([A-Za-z0-9/+=]{40})"),
61
+ group=1,
62
+ ),
63
+ SecretPattern(
64
+ name="generic_assignment",
65
+ regex=re.compile(r"(?i)(password|passwd|secret|token|api[_-]?key)\s*[:=]\s*([\w\-./+=:@]+)"),
66
+ group=2,
67
+ ),
68
+ ]
69
+
70
+
71
+ def _is_env_file(path: Path) -> bool:
72
+ name = path.name
73
+ if name == ".env" or name.startswith(".env.") or name.endswith(".env"):
74
+ return True
75
+ return name in ENV_FILE_PATTERNS
76
+
77
+
78
+ def _is_binary(data: bytes) -> bool:
79
+ return b"\x00" in data
80
+
81
+
82
+ def iter_repo_files(root: Path) -> Iterator[Path]:
83
+ for dirpath, dirnames, filenames in os.walk(root):
84
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
85
+ for filename in filenames:
86
+ if filename in SKIP_FILES:
87
+ continue
88
+ yield Path(dirpath) / filename
89
+
90
+
91
+ def scan_env_file(path: Path) -> List[Finding]:
92
+ findings: List[Finding] = []
93
+ text = path.read_text(encoding="utf-8", errors="ignore")
94
+ for idx, line in enumerate(text.splitlines(), start=1):
95
+ stripped = line.strip()
96
+ if not stripped or stripped.startswith("#"):
97
+ continue
98
+ if stripped.startswith("export "):
99
+ stripped = stripped[len("export ") :]
100
+ if "=" not in stripped:
101
+ continue
102
+ key, value = stripped.split("=", 1)
103
+ key = key.strip()
104
+ value = value.strip().strip('"').strip("'")
105
+ if SENSITIVE_KEYWORDS.search(key):
106
+ findings.append(Finding(path=path, line_number=idx, key=key, value=value))
107
+ return findings
108
+
109
+
110
+ def scan_file_for_patterns(path: Path) -> List[Finding]:
111
+ data = path.read_bytes()
112
+ if _is_binary(data):
113
+ return []
114
+ text = data.decode("utf-8", errors="ignore")
115
+ findings: List[Finding] = []
116
+ for idx, line in enumerate(text.splitlines(), start=1):
117
+ for pattern in SECRET_PATTERNS:
118
+ match = pattern.regex.search(line)
119
+ if not match:
120
+ continue
121
+ value = match.group(pattern.group)
122
+ findings.append(Finding(path=path, line_number=idx, key=pattern.name, value=value))
123
+ return findings
124
+
125
+
126
+ def scan_repo(root: Path) -> List[Finding]:
127
+ findings: List[Finding] = []
128
+ for path in iter_repo_files(root):
129
+ if _is_env_file(path):
130
+ findings.extend(scan_env_file(path))
131
+ continue
132
+ findings.extend(scan_file_for_patterns(path))
133
+ return findings
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Iterable, List
7
+
8
+ from .crypto import Crypto, is_encrypted_value, wrap_encrypted, unwrap_encrypted
9
+ from .scanner import SECRET_PATTERNS, SENSITIVE_KEYWORDS, _is_env_file, _is_binary
10
+
11
+
12
+ @dataclass
13
+ class TransformResult:
14
+ path: Path
15
+ changed: bool
16
+
17
+
18
+ def _encrypt_env_lines(text: str, crypto: Crypto) -> str:
19
+ lines = text.splitlines()
20
+ changed = False
21
+ for idx, line in enumerate(lines):
22
+ stripped = line.strip()
23
+ if not stripped or stripped.startswith("#"):
24
+ continue
25
+ export_prefix = ""
26
+ if stripped.startswith("export "):
27
+ export_prefix = "export "
28
+ stripped = stripped[len("export ") :]
29
+ if "=" not in stripped:
30
+ continue
31
+ key, value = stripped.split("=", 1)
32
+ key = key.strip()
33
+ raw_value = value.strip()
34
+ quote = ""
35
+ if raw_value.startswith(('"', "'")) and raw_value.endswith(('"', "'")):
36
+ quote = raw_value[0]
37
+ raw_value = raw_value[1:-1]
38
+ if not SENSITIVE_KEYWORDS.search(key):
39
+ continue
40
+ if is_encrypted_value(raw_value):
41
+ continue
42
+ token = crypto.encrypt(raw_value).token
43
+ new_value = wrap_encrypted(token)
44
+ if quote:
45
+ new_value = f"{quote}{new_value}{quote}"
46
+ lines[idx] = f"{export_prefix}{key}={new_value}"
47
+ changed = True
48
+ return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
49
+
50
+
51
+ def _decrypt_env_lines(text: str, crypto: Crypto) -> str:
52
+ lines = text.splitlines()
53
+ for idx, line in enumerate(lines):
54
+ stripped = line.strip()
55
+ if not stripped or stripped.startswith("#"):
56
+ continue
57
+ export_prefix = ""
58
+ if stripped.startswith("export "):
59
+ export_prefix = "export "
60
+ stripped = stripped[len("export ") :]
61
+ if "=" not in stripped:
62
+ continue
63
+ key, value = stripped.split("=", 1)
64
+ key = key.strip()
65
+ raw_value = value.strip()
66
+ quote = ""
67
+ if raw_value.startswith(('"', "'")) and raw_value.endswith(('"', "'")):
68
+ quote = raw_value[0]
69
+ raw_value = raw_value[1:-1]
70
+ token = unwrap_encrypted(raw_value)
71
+ if token is None:
72
+ continue
73
+ plaintext = crypto.decrypt(token)
74
+ new_value = plaintext
75
+ if quote:
76
+ new_value = f"{quote}{new_value}{quote}"
77
+ lines[idx] = f"{export_prefix}{key}={new_value}"
78
+ return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
79
+
80
+
81
+ def _encrypt_generic(text: str, crypto: Crypto) -> str:
82
+ changed = False
83
+
84
+ def replacer(match: re.Match) -> str:
85
+ nonlocal changed
86
+ for pattern in SECRET_PATTERNS:
87
+ inner = pattern.regex.search(match.group(0))
88
+ if inner:
89
+ value = inner.group(pattern.group)
90
+ if is_encrypted_value(value):
91
+ return match.group(0)
92
+ token = crypto.encrypt(value).token
93
+ replaced = match.group(0).replace(value, wrap_encrypted(token), 1)
94
+ changed = True
95
+ return replaced
96
+ return match.group(0)
97
+
98
+ result = text
99
+ for pattern in SECRET_PATTERNS:
100
+ result = pattern.regex.sub(lambda m: replacer(m), result)
101
+ return result
102
+
103
+
104
+ def _decrypt_generic(text: str, crypto: Crypto) -> str:
105
+ def replacer(match: re.Match) -> str:
106
+ value = match.group(0)
107
+ token = unwrap_encrypted(value)
108
+ if token is None:
109
+ return value
110
+ plaintext = crypto.decrypt(token)
111
+ return plaintext
112
+
113
+ return re.sub(r"ENC\[[^\]]+\]", replacer, text)
114
+
115
+
116
+ def encrypt_file(path: Path, crypto: Crypto) -> TransformResult:
117
+ data = path.read_bytes()
118
+ if _is_binary(data):
119
+ return TransformResult(path=path, changed=False)
120
+ text = data.decode("utf-8", errors="ignore")
121
+ if _is_env_file(path):
122
+ new_text = _encrypt_env_lines(text, crypto)
123
+ else:
124
+ new_text = _encrypt_generic(text, crypto)
125
+ changed = new_text != text
126
+ if changed:
127
+ path.write_text(new_text, encoding="utf-8")
128
+ return TransformResult(path=path, changed=changed)
129
+
130
+
131
+ def decrypt_file(path: Path, crypto: Crypto) -> TransformResult:
132
+ data = path.read_bytes()
133
+ if _is_binary(data):
134
+ return TransformResult(path=path, changed=False)
135
+ text = data.decode("utf-8", errors="ignore")
136
+ if _is_env_file(path):
137
+ new_text = _decrypt_env_lines(text, crypto)
138
+ else:
139
+ new_text = _decrypt_generic(text, crypto)
140
+ changed = new_text != text
141
+ if changed:
142
+ path.write_text(new_text, encoding="utf-8")
143
+ return TransformResult(path=path, changed=changed)
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: superencryptx
3
+ Version: 0.1.0
4
+ Summary: Scan a repo for secrets and encrypt/decrypt them in-place.
5
+ Author: Superencrypt Contributors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Superencrypt Contributors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://example.com/superencrypt
29
+ Project-URL: Repository, https://example.com/superencrypt
30
+ Project-URL: Issues, https://example.com/superencrypt/issues
31
+ Keywords: secrets,encryption,git,security,cli
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Security
41
+ Classifier: Topic :: Software Development :: Version Control
42
+ Requires-Python: >=3.10
43
+ Description-Content-Type: text/markdown
44
+ License-File: LICENSE
45
+ Requires-Dist: cryptography>=41.0.0
46
+ Dynamic: license-file
47
+
48
+ # superencrypt
49
+
50
+ CLI to scan a repo for secrets (including env files), encrypt them in-place, and decrypt them later using a key.
51
+
52
+ ## Why
53
+
54
+ `superencrypt` helps you keep accidental secrets out of your repo history by encrypting sensitive values in-place while keeping files versionable.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install superencryptx
60
+ ```
61
+
62
+ ### No venv (recommended)
63
+
64
+ ```bash
65
+ pipx install superencryptx
66
+ ```
67
+
68
+ ### System install (no venv)
69
+
70
+ ```bash
71
+ python3 -m pip install --user superencryptx
72
+ ```
73
+
74
+ ## Quick start
75
+
76
+ ```bash
77
+ # Encrypt in-place (generates a key, prints it, and writes .superencrypt.key)
78
+ superencrypt encrypt
79
+
80
+ # Decrypt in-place (use in CI/CD pipelines)
81
+ superencrypt decrypt --key-file .superencrypt.key
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ ```bash
87
+ # Encrypt in-place (generates a key, prints it, and writes .superencrypt.key)
88
+ superencrypt encrypt
89
+
90
+ # Decrypt in-place (provide key or key file)
91
+ superencrypt decrypt --key-file .superencrypt.key
92
+
93
+ # Scan only (no changes)
94
+ superencrypt scan
95
+ ```
96
+
97
+ ## Pipeline example
98
+
99
+ ```bash
100
+ export SUPERENCRYPT_KEY="$(cat .superencrypt.key)"
101
+ superencrypt decrypt --key "$SUPERENCRYPT_KEY"
102
+ ```
103
+
104
+ ## Notes
105
+
106
+ - Encrypted values are stored as `ENC[<token>]`.
107
+ - Key file `.superencrypt.key` should be protected and not committed.
108
+ - Use `scan` first to review matches.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ python -m venv .venv
114
+ source .venv/bin/activate
115
+ pip install -e .
116
+ ```
@@ -0,0 +1,12 @@
1
+ superencrypt/__init__.py,sha256=tXbRXsO0NE_UV1kIHiZTTQQH0fj0U2KoxxNusu_gzrM,48
2
+ superencrypt/__main__.py,sha256=PSQ4rpL0dG6f-qH4N7H-gD9igQkdHzH4yVZDcW8lfZo,80
3
+ superencrypt/cli.py,sha256=CbrCvNQaSwk20MgJ_xUHBFbI65XFVI7_pPbj9i1Orio,3311
4
+ superencrypt/crypto.py,sha256=eFS0TJwrSaMjHvTVmffZmbKBLb6jbpWXFCKCTGNmnU8,1274
5
+ superencrypt/scanner.py,sha256=wbmoXWStPRbiNoXdYgV065EzWa42yM2FA3jUW27tI5Y,3449
6
+ superencrypt/transform.py,sha256=YHIiXZI7gLnQHvivska32ebLJDr5Fn_tjaVj5hCRsys,4789
7
+ superencryptx-0.1.0.dist-info/licenses/LICENSE,sha256=igcdaUQXkh4o1sJMU-uvtT-yp4jPqKuQDs4ado9qL9E,1082
8
+ superencryptx-0.1.0.dist-info/METADATA,sha256=dW2RW-94YzHYPFxkzSdgNjhrpoMuJK9Rbt5wamEU9uA,3636
9
+ superencryptx-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ superencryptx-0.1.0.dist-info/entry_points.txt,sha256=IzMqSqTogHYApIGSkg9S4wB_OysvTo7fwKcJb6INDLs,55
11
+ superencryptx-0.1.0.dist-info/top_level.txt,sha256=w2hHm19sEIAkucCExR6tZ_JBIUaGbv3LkhZc66kjsCI,13
12
+ superencryptx-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ superencrypt = superencrypt.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Superencrypt 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.
@@ -0,0 +1 @@
1
+ superencrypt