nebbia 1.0.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.
nebbia-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: nebbia
3
+ Version: 1.0.0
4
+ Summary: LDAP filter obfuscation tool
5
+ Author: Fasertio
6
+ License: MIT
7
+ Keywords: ldap,obfuscation,ast,security,active-directory
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Information Technology
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Security
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: black; extra == "dev"
25
+ Requires-Dist: ruff; extra == "dev"
26
+ Requires-Dist: mypy; extra == "dev"
27
+
28
+ # nebbia
29
+
30
+ LDAP filter obfuscation tool.
31
+ Analyze the filter using **Abstract Syntax Tree**, applies structural transformation and return an equivalent query.
32
+ Useful for sneaky query to the Domain Controller or edit tools that queries against the domain (like Certipy).
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install nebbia
40
+ ```
41
+
42
+ dev:
43
+
44
+ ```bash
45
+ git clone https://github.com/Fasertio/nebbia
46
+ cd nebbia
47
+ pip install -e ".[dev]"
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Library usage
53
+
54
+ ```python
55
+ from nebbia import obfuscate, parse, serialize
56
+
57
+ query = "(&(objectClass=person)(uid=jdoe)(!(locked=true)))"
58
+ result = obfuscate(query)
59
+ print(result)
60
+ # es: (&((!(!(\75Id=\6A\64\6F\65))))(oBjEcTcLaSs=\70\65\72son)(!(loCkeD=true)))
61
+
62
+ r1 = obfuscate(query, seed=42)
63
+ r2 = obfuscate(query, seed=42)
64
+ assert r1 == r2
65
+
66
+ from nebbia import parse, serialize, NodeType
67
+
68
+ ast = parse("(sAMAccountName=krbtgt)")
69
+ print(ast)
70
+ # ASTNode(FILTER, 'sAMAccountName'='krbtgt')
71
+
72
+ ast.value = "administrator"
73
+ print(serialize(ast))
74
+ # (sAMAccountName=administrator)
75
+ ```
76
+
77
+ ---
78
+
79
+ ## CLI
80
+
81
+ ```bash
82
+
83
+ #single query
84
+ nebbia "(uid=jdoe)"
85
+
86
+ #multiple
87
+ nebbia "(&(uid=admin)(objectClass=person))" --count 3
88
+
89
+ #deterministic output
90
+ nebbia "(cn=krbtgt)" --seed 42
91
+
92
+ #disable ANSI color
93
+ nebbia "(uid=test)" --no-color
94
+
95
+ #stdin
96
+ echo "(uid=admin)" | nebbia
97
+
98
+ #python module
99
+ python -m nebbia "(uid=jdoe)" --count 2
100
+ ```
101
+
102
+ ---
103
+
104
+ ## License
105
+
106
+ MIT
nebbia-1.0.0/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # nebbia
2
+
3
+ LDAP filter obfuscation tool.
4
+ Analyze the filter using **Abstract Syntax Tree**, applies structural transformation and return an equivalent query.
5
+ Useful for sneaky query to the Domain Controller or edit tools that queries against the domain (like Certipy).
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install nebbia
13
+ ```
14
+
15
+ dev:
16
+
17
+ ```bash
18
+ git clone https://github.com/Fasertio/nebbia
19
+ cd nebbia
20
+ pip install -e ".[dev]"
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Library usage
26
+
27
+ ```python
28
+ from nebbia import obfuscate, parse, serialize
29
+
30
+ query = "(&(objectClass=person)(uid=jdoe)(!(locked=true)))"
31
+ result = obfuscate(query)
32
+ print(result)
33
+ # es: (&((!(!(\75Id=\6A\64\6F\65))))(oBjEcTcLaSs=\70\65\72son)(!(loCkeD=true)))
34
+
35
+ r1 = obfuscate(query, seed=42)
36
+ r2 = obfuscate(query, seed=42)
37
+ assert r1 == r2
38
+
39
+ from nebbia import parse, serialize, NodeType
40
+
41
+ ast = parse("(sAMAccountName=krbtgt)")
42
+ print(ast)
43
+ # ASTNode(FILTER, 'sAMAccountName'='krbtgt')
44
+
45
+ ast.value = "administrator"
46
+ print(serialize(ast))
47
+ # (sAMAccountName=administrator)
48
+ ```
49
+
50
+ ---
51
+
52
+ ## CLI
53
+
54
+ ```bash
55
+
56
+ #single query
57
+ nebbia "(uid=jdoe)"
58
+
59
+ #multiple
60
+ nebbia "(&(uid=admin)(objectClass=person))" --count 3
61
+
62
+ #deterministic output
63
+ nebbia "(cn=krbtgt)" --seed 42
64
+
65
+ #disable ANSI color
66
+ nebbia "(uid=test)" --no-color
67
+
68
+ #stdin
69
+ echo "(uid=admin)" | nebbia
70
+
71
+ #python module
72
+ python -m nebbia "(uid=jdoe)" --count 2
73
+ ```
74
+
75
+ ---
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,37 @@
1
+ """
2
+ nebbia
3
+ ===============
4
+ LDAP filter obfuscation tool.
5
+
6
+ Usage:
7
+ ---------------
8
+ >>> from nebbia import obfuscate
9
+ >>> obfuscate("(&(uid=jdoe)(objectClass=person))")
10
+ '(&(oBjEcTcLaSs=\\\\70\\\\65\\\\72\\\\73\\\\6F\\\\6E)(uId=\\\\6A\\\\64\\\\6F\\\\65))'
11
+
12
+ >>> from nebbia import parse, serialize
13
+ >>> ast = parse("(uid=admin)")
14
+ >>> serialize(ast)
15
+ '(uid=admin)'
16
+ """
17
+
18
+ from .core import (
19
+ ASTNode,
20
+ LDAPParseError,
21
+ NodeType,
22
+ obfuscate,
23
+ parse,
24
+ serialize,
25
+ )
26
+
27
+ __all__ = [
28
+ "obfuscate",
29
+ "parse",
30
+ "serialize",
31
+ "ASTNode",
32
+ "NodeType",
33
+ "LDAPParseError",
34
+ ]
35
+
36
+ __version__ = "1.0.0"
37
+ __author__ = "Fasertio"
@@ -0,0 +1,138 @@
1
+ """
2
+ Entrypoint CLI.
3
+
4
+ Usage
5
+ --------
6
+ # via module
7
+ python -m nebbia "(uid=jdoe)"
8
+
9
+ # pip installation
10
+ nebbia "(uid=jdoe)"
11
+ nebbia "(uid=jdoe)" --count 3
12
+ nebbia "(uid=jdoe)" --seed 42
13
+ nebbia --demo
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import sys
20
+
21
+ from .core import LDAPParseError, obfuscate
22
+
23
+
24
+ def _build_parser() -> argparse.ArgumentParser:
25
+ p = argparse.ArgumentParser(
26
+ prog="nebbia",
27
+ description=(
28
+ "LDAP filter obfuscation tool.\n"
29
+ "Structural AST transformation applied."
30
+ ),
31
+ formatter_class=argparse.RawDescriptionHelpFormatter,
32
+ epilog=(
33
+ "Example of usage:\n"
34
+ ' nebbia "(uid=jdoe)"\n'
35
+ ' nebbia "(&(uid=admin)(objectClass=person))" --count 3\n'
36
+ ' nebbia "(cn=krbtgt)" --seed 42 --no-color'
37
+ ),
38
+ )
39
+ p.add_argument(
40
+ "query",
41
+ nargs="?",
42
+ help="LDAP filter.",
43
+ )
44
+ p.add_argument(
45
+ "-c", "--count",
46
+ type=int,
47
+ default=1,
48
+ metavar="N",
49
+ help="Number of variant (default: 1).",
50
+ )
51
+ p.add_argument(
52
+ "-s", "--seed",
53
+ type=int,
54
+ default=None,
55
+ metavar="SEED",
56
+ help="Output seed.",
57
+ )
58
+ p.add_argument(
59
+ "--no-color",
60
+ action="store_true",
61
+ help="Disable color output ANSI.",
62
+ )
63
+ p.add_argument(
64
+ "-V", "--version",
65
+ action="version",
66
+ version="nebbia 1.0.0",
67
+ )
68
+ return p
69
+
70
+
71
+ _RESET = "\033[0m"
72
+ _BOLD = "\033[1m"
73
+ _CYAN = "\033[36m"
74
+ _GREEN = "\033[32m"
75
+ _YELLOW = "\033[33m"
76
+ _RED = "\033[31m"
77
+ _DIM = "\033[2m"
78
+
79
+
80
+ def _c(text: str, *codes: str, use_color: bool = True) -> str:
81
+ if not use_color:
82
+ return text
83
+ return "".join(codes) + text + _RESET
84
+
85
+
86
+ def _run_query(
87
+ query: str,
88
+ count: int,
89
+ seed: int | None,
90
+ use_color: bool,
91
+ ) -> int:
92
+ """Obfuscated variables for single query"""
93
+ sep = _c("─" * 60, _DIM, use_color=use_color)
94
+ print(sep)
95
+ print(_c("INPUT ", _BOLD, _CYAN, use_color=use_color) + query)
96
+
97
+ ok = True
98
+ for i in range(1, count + 1):
99
+ current_seed = None if seed is None else seed + i - 1
100
+ try:
101
+ result = obfuscate(query, seed=current_seed)
102
+ label = f"RESULT {i}" if count > 1 else "RESULT "
103
+ print(_c(f"{label:<11}", _BOLD, _GREEN, use_color=use_color) + result)
104
+ except LDAPParseError as exc:
105
+ print(_c("ERROR ", _BOLD, _RED, use_color=use_color) + str(exc))
106
+ ok = False
107
+ break
108
+
109
+ return 0 if ok else 1
110
+
111
+
112
+ def main(argv: list[str] | None = None) -> None:
113
+ parser = _build_parser()
114
+ args = parser.parse_args(argv)
115
+
116
+ use_color = not args.no_color and sys.stdout.isatty()
117
+
118
+ if args.query:
119
+ rc = _run_query(args.query, count=args.count, seed=args.seed, use_color=use_color)
120
+ sys.exit(rc)
121
+
122
+ #stdin
123
+ if not sys.stdin.isatty():
124
+ exit_code = 0
125
+ for line in sys.stdin:
126
+ line = line.strip()
127
+ if line:
128
+ rc = _run_query(line, count=args.count, seed=args.seed, use_color=use_color)
129
+ if rc:
130
+ exit_code = rc
131
+ sys.exit(exit_code)
132
+
133
+ parser.print_help()
134
+ sys.exit(0)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,253 @@
1
+ """
2
+ nebbia.core
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ import random
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum, auto
11
+ from typing import Optional
12
+
13
+
14
+ #AST Node
15
+ class NodeType(Enum):
16
+ AND = auto()
17
+ OR = auto()
18
+ NOT = auto()
19
+ FILTER = auto() # (attr op value)
20
+ PRESENT = auto() # (attr=*)
21
+
22
+
23
+ @dataclass
24
+ class ASTNode:
25
+ type: NodeType
26
+ children: list[ASTNode] = field(default_factory=list)
27
+ attribute: Optional[str] = None
28
+ operator: Optional[str] = None
29
+ value: Optional[str] = None
30
+
31
+ def __repr__(self) -> str:
32
+ if self.type == NodeType.FILTER:
33
+ return f"ASTNode(FILTER, {self.attribute!r}{self.operator}{self.value!r})"
34
+ if self.type == NodeType.PRESENT:
35
+ return f"ASTNode(PRESENT, {self.attribute!r}=*)"
36
+ return f"ASTNode({self.type.name}, children={len(self.children)})"
37
+
38
+
39
+ #Parser
40
+
41
+ class LDAPParseError(ValueError):
42
+ """Malformed LDAP query"""
43
+
44
+
45
+ class LDAPParser:
46
+ """LDAP Parser"""
47
+
48
+ def __init__(self, query: str) -> None:
49
+ self.src = query.strip()
50
+ self.pos = 0
51
+
52
+ def _peek(self) -> str:
53
+ return self.src[self.pos] if self.pos < len(self.src) else ""
54
+
55
+ def _consume(self, ch: str) -> None:
56
+ if self.pos >= len(self.src):
57
+ raise LDAPParseError(
58
+ f"Unexpected line end '{ch}'"
59
+ )
60
+ if self.src[self.pos] != ch:
61
+ ctx = self.src[max(0, self.pos - 5): self.pos + 5]
62
+ raise LDAPParseError(
63
+ f"Expected '{ch}', found '{self.src[self.pos]}' "
64
+ f"at pos {self.pos} (context: '...{ctx}...')"
65
+ )
66
+ self.pos += 1
67
+
68
+
69
+ def parse(self) -> ASTNode:
70
+ node = self._parse_filter()
71
+ if self.pos != len(self.src):
72
+ raise LDAPParseError(
73
+ f"Input not used at pos {self.pos}: '{self.src[self.pos:]}'"
74
+ )
75
+ return node
76
+
77
+ def _parse_filter(self) -> ASTNode:
78
+ self._consume("(")
79
+ ch = self._peek()
80
+ if ch == "&":
81
+ node = self._parse_compound(NodeType.AND)
82
+ elif ch == "|":
83
+ node = self._parse_compound(NodeType.OR)
84
+ elif ch == "!":
85
+ node = self._parse_not()
86
+ else:
87
+ node = self._parse_simple()
88
+ self._consume(")")
89
+ return node
90
+
91
+ def _parse_compound(self, ntype: NodeType) -> ASTNode:
92
+ self.pos += 1
93
+ node = ASTNode(type=ntype)
94
+ while self._peek() == "(":
95
+ node.children.append(self._parse_filter())
96
+ return node
97
+
98
+ def _parse_not(self) -> ASTNode:
99
+ self.pos += 1
100
+ node = ASTNode(type=NodeType.NOT)
101
+ node.children.append(self._parse_filter())
102
+ return node
103
+
104
+ def _parse_simple(self) -> ASTNode:
105
+ try:
106
+ end = self.src.index(")", self.pos)
107
+ except ValueError:
108
+ raise LDAPParseError("Expected ')'")
109
+
110
+ expr = self.src[self.pos:end]
111
+ self.pos = end
112
+
113
+ m = re.match(r"^([^=<>~!]+)=\*$", expr)
114
+ if m:
115
+ return ASTNode(type=NodeType.PRESENT, attribute=m.group(1))
116
+
117
+ m = re.match(r"^([^=<>~!]+)(>=|<=|~=|=)(.*)$", expr)
118
+ if m:
119
+ return ASTNode(
120
+ type=NodeType.FILTER,
121
+ attribute=m.group(1),
122
+ operator=m.group(2),
123
+ value=m.group(3),
124
+ )
125
+
126
+ raise LDAPParseError(f"Simple filter not recognized: '{expr}'")
127
+
128
+
129
+ def _case_randomize(s: str) -> str:
130
+ """Random mixed-case"""
131
+ return "".join(c.upper() if random.random() > 0.5 else c.lower() for c in s)
132
+
133
+
134
+ def _hex_encode_full(value: str) -> str:
135
+ """LDAP escape \\HH."""
136
+ return "".join(f"\\{ord(c):02X}" for c in value)
137
+
138
+
139
+ def _hex_encode_partial(value: str) -> str:
140
+ """Alphanumeric encoding"""
141
+ return "".join(
142
+ f"\\{ord(c):02X}" if c.isalnum() and random.random() > 0.4 else c
143
+ for c in value
144
+ )
145
+
146
+
147
+ def _obfuscate_value(value: str) -> str:
148
+ strategy = random.choice(["plain", "full_hex", "partial_hex"])
149
+ if strategy == "full_hex":
150
+ return _hex_encode_full(value)
151
+ if strategy == "partial_hex":
152
+ return _hex_encode_partial(value)
153
+ return value
154
+
155
+
156
+ def _double_negation(node: ASTNode) -> ASTNode:
157
+ return ASTNode(
158
+ type=NodeType.NOT,
159
+ children=[ASTNode(type=NodeType.NOT, children=[node])],
160
+ )
161
+
162
+
163
+ def _shuffle_children(node: ASTNode) -> ASTNode:
164
+ """AND/OR child mix"""
165
+ if node.type in (NodeType.AND, NodeType.OR) and node.children:
166
+ random.shuffle(node.children)
167
+ return node
168
+
169
+
170
+ def _transform(node: ASTNode, depth: int = 0) -> ASTNode:
171
+ """Recursive transformation"""
172
+
173
+ # ricorsione sui figli
174
+ node.children = [_transform(c, depth + 1) for c in node.children]
175
+
176
+ # ── offuscamento lessicale ───────────────
177
+ if node.attribute:
178
+ node.attribute = _case_randomize(node.attribute)
179
+
180
+ if node.type == NodeType.FILTER and node.value is not None:
181
+ node.value = _obfuscate_value(node.value)
182
+
183
+ # ── trasformazioni strutturali ───────────
184
+ if node.type in (NodeType.AND, NodeType.OR):
185
+ node = _shuffle_children(node)
186
+ # doppia negazione su un figlio casuale con prob 30 %
187
+ if node.children and random.random() < 0.30:
188
+ idx = random.randrange(len(node.children))
189
+ node.children[idx] = _double_negation(node.children[idx])
190
+
191
+ elif node.type == NodeType.FILTER and depth > 0 and random.random() < 0.20:
192
+ node = _double_negation(node)
193
+
194
+ return node
195
+
196
+
197
+ def serialize(node: ASTNode) -> str:
198
+ """ASTNode conversion"""
199
+ match node.type:
200
+ case NodeType.AND:
201
+ return "(&" + "".join(serialize(c) for c in node.children) + ")"
202
+ case NodeType.OR:
203
+ return "(|" + "".join(serialize(c) for c in node.children) + ")"
204
+ case NodeType.NOT:
205
+ return "(!" + serialize(node.children[0]) + ")"
206
+ case NodeType.PRESENT:
207
+ return f"({node.attribute}=*)"
208
+ case NodeType.FILTER:
209
+ return f"({node.attribute}{node.operator}{node.value})"
210
+ case _:
211
+ raise LDAPParseError(f"Unknown node: {node.type}")
212
+
213
+
214
+ #public function
215
+ def obfuscate(query: str, seed: Optional[int] = None) -> str:
216
+ """
217
+ Parameters
218
+ ----------
219
+ query : str
220
+ seed : int, optional
221
+
222
+ Returns
223
+ -------
224
+ str
225
+
226
+ Raises
227
+ ------
228
+ LDAPParseError
229
+
230
+ Examples
231
+ --------
232
+ >>> from nebbia import obfuscate
233
+ >>> obfuscate("(uid=jdoe)", seed=42)
234
+ '(uId=\\\\6A\\\\64\\\\6F\\\\65)'
235
+ """
236
+ if seed is not None:
237
+ random.seed(seed)
238
+ ast = LDAPParser(query).parse()
239
+ ast = _transform(ast)
240
+ return serialize(ast)
241
+
242
+
243
+ def parse(query: str) -> ASTNode:
244
+ """
245
+ Parameters
246
+ ----------
247
+ query : str
248
+
249
+ Returns
250
+ -------
251
+ ASTNode
252
+ """
253
+ return LDAPParser(query).parse()
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: nebbia
3
+ Version: 1.0.0
4
+ Summary: LDAP filter obfuscation tool
5
+ Author: Fasertio
6
+ License: MIT
7
+ Keywords: ldap,obfuscation,ast,security,active-directory
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Information Technology
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Security
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Requires-Dist: pytest-cov; extra == "dev"
24
+ Requires-Dist: black; extra == "dev"
25
+ Requires-Dist: ruff; extra == "dev"
26
+ Requires-Dist: mypy; extra == "dev"
27
+
28
+ # nebbia
29
+
30
+ LDAP filter obfuscation tool.
31
+ Analyze the filter using **Abstract Syntax Tree**, applies structural transformation and return an equivalent query.
32
+ Useful for sneaky query to the Domain Controller or edit tools that queries against the domain (like Certipy).
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install nebbia
40
+ ```
41
+
42
+ dev:
43
+
44
+ ```bash
45
+ git clone https://github.com/Fasertio/nebbia
46
+ cd nebbia
47
+ pip install -e ".[dev]"
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Library usage
53
+
54
+ ```python
55
+ from nebbia import obfuscate, parse, serialize
56
+
57
+ query = "(&(objectClass=person)(uid=jdoe)(!(locked=true)))"
58
+ result = obfuscate(query)
59
+ print(result)
60
+ # es: (&((!(!(\75Id=\6A\64\6F\65))))(oBjEcTcLaSs=\70\65\72son)(!(loCkeD=true)))
61
+
62
+ r1 = obfuscate(query, seed=42)
63
+ r2 = obfuscate(query, seed=42)
64
+ assert r1 == r2
65
+
66
+ from nebbia import parse, serialize, NodeType
67
+
68
+ ast = parse("(sAMAccountName=krbtgt)")
69
+ print(ast)
70
+ # ASTNode(FILTER, 'sAMAccountName'='krbtgt')
71
+
72
+ ast.value = "administrator"
73
+ print(serialize(ast))
74
+ # (sAMAccountName=administrator)
75
+ ```
76
+
77
+ ---
78
+
79
+ ## CLI
80
+
81
+ ```bash
82
+
83
+ #single query
84
+ nebbia "(uid=jdoe)"
85
+
86
+ #multiple
87
+ nebbia "(&(uid=admin)(objectClass=person))" --count 3
88
+
89
+ #deterministic output
90
+ nebbia "(cn=krbtgt)" --seed 42
91
+
92
+ #disable ANSI color
93
+ nebbia "(uid=test)" --no-color
94
+
95
+ #stdin
96
+ echo "(uid=admin)" | nebbia
97
+
98
+ #python module
99
+ python -m nebbia "(uid=jdoe)" --count 2
100
+ ```
101
+
102
+ ---
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ nebbia/__init__.py
4
+ nebbia/__main__.py
5
+ nebbia/core.py
6
+ nebbia.egg-info/PKG-INFO
7
+ nebbia.egg-info/SOURCES.txt
8
+ nebbia.egg-info/dependency_links.txt
9
+ nebbia.egg-info/entry_points.txt
10
+ nebbia.egg-info/requires.txt
11
+ nebbia.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nebbia = nebbia.__main__:main
@@ -0,0 +1,7 @@
1
+
2
+ [dev]
3
+ pytest>=7
4
+ pytest-cov
5
+ black
6
+ ruff
7
+ mypy
@@ -0,0 +1,3 @@
1
+ build
2
+ dist
3
+ nebbia
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ # ── Metadati pacchetto ───────────────────────────────────────────────────
6
+ [project]
7
+ name = "nebbia"
8
+ version = "1.0.0"
9
+ description = "LDAP filter obfuscation tool"
10
+ readme = "README.md"
11
+ license = { text = "MIT" }
12
+ requires-python = ">=3.10"
13
+ keywords = ["ldap", "obfuscation", "ast", "security", "active-directory"]
14
+
15
+ authors = [
16
+ { name = "Fasertio" },
17
+ ]
18
+
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Information Technology",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Security",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
31
+ ]
32
+
33
+ dependencies = []
34
+
35
+ [project.optional-dependencies]
36
+ dev = [
37
+ "pytest>=7",
38
+ "pytest-cov",
39
+ "black",
40
+ "ruff",
41
+ "mypy",
42
+ ]
43
+
44
+ # ── Entrypoint CLI ───────────────────────────────────────────────────────
45
+ [project.scripts]
46
+ nebbia = "nebbia.__main__:main"
47
+
48
+ # ── Setuptools config ────────────────────────────────────────────────────
49
+ [tool.setuptools.packages.find]
50
+ where = ["."]
51
+
52
+ [tool.setuptools.package-data]
53
+ nebbia = ["py.typed"]
54
+
55
+ # ── Tool: black ──────────────────────────────────────────────────────────
56
+ [tool.black]
57
+ line-length = 100
58
+ target-version = ["py310"]
59
+
60
+ # ── Tool: ruff ───────────────────────────────────────────────────────────
61
+ [tool.ruff]
62
+ line-length = 100
63
+ select = ["E", "F", "I", "UP"]
64
+
65
+ # ── Tool: mypy ───────────────────────────────────────────────────────────
66
+ [tool.mypy]
67
+ python_version = "3.10"
68
+ strict = true
69
+ ignore_missing_imports = true
70
+
71
+ # ── Tool: pytest ─────────────────────────────────────────────────────────
72
+ [tool.pytest.ini_options]
73
+ testpaths = ["tests"]
74
+ addopts = "-v --tb=short"
nebbia-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+