attackmap-analyzer-swift 0.2.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,58 @@
1
+ """CocoaPods SBOM: ``Podfile.lock`` (swift#1).
2
+
3
+ ``PODS:`` lists every resolved pod (`` - Name (1.2.3)``, sub-dependencies
4
+ indented below it); ``DEPENDENCIES:`` lists the ones the Podfile asks for,
5
+ which makes them direct. Subspecs (``Firebase/Core``) are folded into their
6
+ root pod. A line-based reader: garbage or truncated input yields what it
7
+ could read and never raises.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from dataclasses import dataclass
14
+
15
+ # ` - AFNetworking (4.0.1):` / ` - "GoogleUtilities/Environment (7.11.0)"`
16
+ _POD_ENTRY = re.compile(r'^ - "?([^\s"(][^"(]{0,200}?) \(([^()\s]{1,64})\)"?:?\s*$')
17
+ # ` - AFNetworking (~> 4.0)` / ` - Firebase/Core` / ` - MyPod (from `../MyPod`)`
18
+ _DEPENDENCY = re.compile(r'^ - "?([^\s"(][^"(\s]{0,200})')
19
+ _SECTION = re.compile(r"^([A-Z][A-Z ]{1,40}):")
20
+ _MAX_LINES = 200_000
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Pod:
25
+ name: str
26
+ version: str
27
+ line: int
28
+ direct: bool
29
+
30
+
31
+ def _root(name: str) -> str:
32
+ return name.split("/", 1)[0].strip()
33
+
34
+
35
+ def parse_podfile_lock(text: str) -> list[Pod]:
36
+ """Resolved pods (root names, one per pod) with direct/transitive."""
37
+ resolved: dict[str, tuple[str, int]] = {}
38
+ direct: set[str] = set()
39
+ section = ""
40
+ for number, line in enumerate(text.splitlines()[:_MAX_LINES], start=1):
41
+ header = _SECTION.match(line)
42
+ if header:
43
+ section = header.group(1)
44
+ continue
45
+ if section == "PODS":
46
+ m = _POD_ENTRY.match(line)
47
+ if m:
48
+ root = _root(m.group(1))
49
+ if root and root not in resolved:
50
+ resolved[root] = (m.group(2), number)
51
+ elif section == "DEPENDENCIES":
52
+ m = _DEPENDENCY.match(line)
53
+ if m:
54
+ direct.add(_root(m.group(1)))
55
+ return [Pod(name, version, line, name in direct) for name, (version, line) in resolved.items()]
56
+
57
+
58
+ __all__ = ["Pod", "parse_podfile_lock"]
@@ -0,0 +1,239 @@
1
+ """Linear Swift source helpers: bracket matching that understands Swift string
2
+ literals (interpolation, multi-line, raw ``#"..."#``) and nested comments.
3
+
4
+ Everything here runs on hostile input, so it is one forward pass per file with
5
+ bounded work per character, never raises on garbage, and never recurses.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import bisect
11
+ import functools
12
+ import re
13
+ from dataclasses import dataclass, field
14
+
15
+ # Code mode: the next string start, comment start or bracket.
16
+ _CODE_TOKEN = re.compile(r'#*"""|#*"|//|/\*|[()\[\]{}]')
17
+ _BLOCK_COMMENT_TOKEN = re.compile(r"/\*|\*/")
18
+ _OPEN_TO_CLOSE = {"(": ")", "[": "]", "{": "}"}
19
+
20
+
21
+ @dataclass
22
+ class Source:
23
+ """Bracket pairs (both directions) for code brackets, and the sorted
24
+ spans of string literals and comments (``masked``)."""
25
+
26
+ text: str
27
+ pairs: dict[int, int] = field(default_factory=dict)
28
+ rpairs: dict[int, int] = field(default_factory=dict)
29
+ masked: list[tuple[int, int]] = field(default_factory=list)
30
+ strings: list[tuple[int, int]] = field(default_factory=list)
31
+ _starts: list[int] = field(default_factory=list)
32
+
33
+ def in_masked(self, offset: int) -> bool:
34
+ """True when ``offset`` falls inside a string literal or comment."""
35
+ i = bisect.bisect_right(self._starts, offset) - 1
36
+ return i >= 0 and self.masked[i][0] <= offset < self.masked[i][1]
37
+
38
+ def masked_span(self, offset: int) -> tuple[int, int] | None:
39
+ """The string or comment span containing ``offset``, if any."""
40
+ i = bisect.bisect_right(self._starts, offset) - 1
41
+ if i >= 0 and self.masked[i][0] <= offset < self.masked[i][1]:
42
+ return self.masked[i]
43
+ return None
44
+
45
+ def close_of(self, offset: int) -> int:
46
+ """Offset of the bracket closing the one at ``offset``, or -1."""
47
+ return self.pairs.get(offset, -1)
48
+
49
+ def open_of(self, offset: int) -> int:
50
+ return self.rpairs.get(offset, -1)
51
+
52
+
53
+ @functools.lru_cache(maxsize=8)
54
+ def parse(text: str) -> Source:
55
+ src = Source(text)
56
+ stack: list[tuple[str, int]] = []
57
+ n = len(text)
58
+ pos = 0
59
+ while pos < n:
60
+ m = _CODE_TOKEN.search(text, pos)
61
+ if m is None:
62
+ break
63
+ tok = m.group(0)
64
+ start = m.start()
65
+ if tok == "//":
66
+ end = text.find("\n", start)
67
+ end = n if end < 0 else end
68
+ src.masked.append((start, end))
69
+ pos = end
70
+ elif tok == "/*":
71
+ end = _skip_block_comment(text, start)
72
+ src.masked.append((start, end))
73
+ pos = end
74
+ elif tok.endswith('"'):
75
+ end = _skip_string(text, start, tok)
76
+ src.masked.append((start, end))
77
+ src.strings.append((start, end))
78
+ pos = end
79
+ elif tok in _OPEN_TO_CLOSE:
80
+ stack.append((tok, start))
81
+ pos = m.end()
82
+ else:
83
+ # A closer: pop to its opener; an unmatched closer is ignored.
84
+ for depth in range(len(stack) - 1, max(-1, len(stack) - 8), -1):
85
+ if _OPEN_TO_CLOSE[stack[depth][0]] == tok:
86
+ open_at = stack[depth][1]
87
+ del stack[depth:]
88
+ src.pairs[open_at] = start
89
+ src.rpairs[start] = open_at
90
+ break
91
+ pos = m.end()
92
+ src._starts = [s for s, _ in src.masked]
93
+ return src
94
+
95
+
96
+ def _skip_block_comment(text: str, start: int) -> int:
97
+ """End offset of the (nestable) block comment starting at ``start``."""
98
+ depth = 0
99
+ pos = start
100
+ while True:
101
+ m = _BLOCK_COMMENT_TOKEN.search(text, pos)
102
+ if m is None:
103
+ return len(text)
104
+ depth += 1 if m.group(0) == "/*" else -1
105
+ pos = m.end()
106
+ if depth == 0:
107
+ return pos
108
+
109
+
110
+ # Interpolations nested deeper than this end the literal (hostile input).
111
+ _MAX_NESTING = 16
112
+
113
+
114
+ @functools.lru_cache(maxsize=32)
115
+ def _string_token(hashes: int, multiline: bool) -> re.Pattern[str]:
116
+ closer = ('"""' if multiline else '"') + "#" * hashes
117
+ escape = "\\" + "#" * hashes
118
+ alternatives = [re.escape(closer), re.escape(escape)]
119
+ if not multiline:
120
+ alternatives.append("\n")
121
+ return re.compile("|".join(alternatives))
122
+
123
+
124
+ def _skip_string(text: str, start: int, opener: str, depth: int = 0) -> int:
125
+ """End offset of the string literal starting at ``start``. Interpolations
126
+ (``\\(...)``) are skipped as code, including strings nested in them."""
127
+ hashes = len(opener) - len(opener.lstrip("#"))
128
+ multiline = opener.endswith('"""')
129
+ token = _string_token(hashes, multiline)
130
+ escape_len = 1 + hashes
131
+ pos = start + len(opener)
132
+ n = len(text)
133
+ while pos < n:
134
+ m = token.search(text, pos)
135
+ if m is None:
136
+ return n
137
+ tok = m.group(0)
138
+ if tok == "\n":
139
+ return m.start() # unterminated single-line string
140
+ if tok.startswith("\\"):
141
+ after = m.start() + escape_len
142
+ if after < n and text[after] == "(":
143
+ if depth >= _MAX_NESTING:
144
+ return n
145
+ pos = _skip_interpolation(text, after, depth + 1)
146
+ else:
147
+ pos = after + 1 # escaped character
148
+ continue
149
+ return m.end()
150
+ return n
151
+
152
+
153
+ _INTERP_TOKEN = re.compile(r'#*"""|#*"|[()]')
154
+
155
+
156
+ def _skip_interpolation(text: str, paren: int, depth: int) -> int:
157
+ """Offset just past the ``)`` closing the interpolation at ``paren``."""
158
+ parens = 0
159
+ pos = paren
160
+ n = len(text)
161
+ while pos < n:
162
+ m = _INTERP_TOKEN.search(text, pos)
163
+ if m is None:
164
+ return n
165
+ tok = m.group(0)
166
+ if tok == "(":
167
+ parens += 1
168
+ pos = m.end()
169
+ elif tok == ")":
170
+ parens -= 1
171
+ pos = m.end()
172
+ if parens == 0:
173
+ return pos
174
+ else:
175
+ pos = _skip_string(text, m.start(), tok, depth)
176
+ return n
177
+
178
+
179
+ # ---------- Small helpers over a parsed Source ----------
180
+
181
+ _STRING_LITERAL = re.compile(r'"((?:[^"\\\n]|\\.)*)"')
182
+
183
+
184
+ def string_value(text: str) -> str | None:
185
+ """The value of a plain, interpolation-free string literal expression
186
+ (``"users"``), else None."""
187
+ stripped = text.strip()
188
+ m = _STRING_LITERAL.fullmatch(stripped)
189
+ if m is None or "\\(" in m.group(1):
190
+ return None
191
+ return m.group(1)
192
+
193
+
194
+ def split_args(src: Source, open_paren: int, close_paren: int) -> list[tuple[int, int]]:
195
+ """Top-level argument spans between a matched pair of brackets."""
196
+ text = src.text
197
+ spans: list[tuple[int, int]] = []
198
+ pos = open_paren + 1
199
+ start = pos
200
+ while pos < close_paren:
201
+ ch = text[pos]
202
+ if ch in "([{":
203
+ close = src.close_of(pos)
204
+ if close < 0 or close > close_paren:
205
+ break
206
+ pos = close + 1
207
+ continue
208
+ if ch == '"' or (ch == "#" and pos + 1 < close_paren and text[pos + 1] in '#"'):
209
+ i = bisect.bisect_right(src._starts, pos) - 1
210
+ if i >= 0 and src.masked[i][0] == pos:
211
+ pos = max(src.masked[i][1], pos + 1)
212
+ continue
213
+ if ch == "/" and src.in_masked(pos):
214
+ i = bisect.bisect_right(src._starts, pos) - 1
215
+ pos = max(src.masked[i][1], pos + 1)
216
+ continue
217
+ if ch == ",":
218
+ spans.append((start, pos))
219
+ start = pos + 1
220
+ pos += 1
221
+ if text[start:close_paren].strip():
222
+ spans.append((start, close_paren))
223
+ return spans
224
+
225
+
226
+ _LABEL = re.compile(r"\s*([A-Za-z_]\w*)\s*:(?!:)")
227
+
228
+
229
+ def arg_label(text: str) -> tuple[str | None, str]:
230
+ """Split ``label: value`` into (label, value); (None, text) if unlabeled."""
231
+ m = _LABEL.match(text)
232
+ if m is None:
233
+ return None, text.strip()
234
+ return m.group(1), text[m.end():].strip()
235
+
236
+
237
+ def clip(text: str, limit: int = 200) -> str:
238
+ text = " ".join(text.split())
239
+ return text if len(text) <= limit else text[: limit - 1] + "…"
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: attackmap-analyzer-swift
3
+ Version: 0.2.0
4
+ Summary: Swift ecosystem analyzer plugin for AttackMap: Vapor/Hummingbird routes with route-level auth, the iOS/macOS app attack surface, and a SwiftPM/CocoaPods/Carthage SBOM.
5
+ Author: AttackMap Contributors
6
+ Author-email: Matthew Davis <mlaify@mlaify.io>
7
+ Maintainer-email: Matthew Davis <mlaify@mlaify.io>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/mlaify/attackmap-analyzer-swift
10
+ Project-URL: Repository, https://github.com/mlaify/attackmap-analyzer-swift
11
+ Project-URL: Issues, https://github.com/mlaify/attackmap-analyzer-swift/issues
12
+ Project-URL: Changelog, https://github.com/mlaify/attackmap-analyzer-swift/blob/main/CHANGELOG.md
13
+ Project-URL: AttackMap core, https://github.com/mlaify/AttackMap
14
+ Keywords: security,static-analysis,appsec,defensive-security,attackmap,attackmap-plugin,swift
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: System Administrators
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Software Development :: Quality Assurance
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: pydantic>=2.7.0
32
+ Provides-Extra: core
33
+ Requires-Dist: attackmap>=0.6.0; extra == "core"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
36
+ Requires-Dist: build>=1.2.0; extra == "dev"
37
+ Requires-Dist: attackmap>=0.6.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # attackmap-analyzer-swift
41
+
42
+ > [!IMPORTANT]
43
+ > **Active development, slow pace.** AttackMap is under active development, but
44
+ > progress may be slow until more contributors or co-maintainers join. Help is
45
+ > very welcome with the core engine, an analyzer, the macOS app, or the docs —
46
+ > see [CONTRIBUTING.md](CONTRIBUTING.md) or open an issue on
47
+ > [mlaify/AttackMap](https://github.com/mlaify/AttackMap/issues) to say hello.
48
+ > Security reports are still welcome at [security@mlaify.io](mailto:security@mlaify.io).
49
+
50
+ Swift ecosystem analyzer plugin for [AttackMap](https://github.com/mlaify/AttackMap).
51
+ Auto-discovered via the `attackmap.analyzers` entry point once installed.
52
+
53
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
+
55
+ > **Status: experimental.** Vapor / Hummingbird routes with route-level auth, the
56
+ > iOS/macOS app attack surface, and a SwiftPM + CocoaPods + Carthage dependency SBOM.
57
+ > Requires an AttackMap core with the app-surface contract (mlaify/AttackMap#347).
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ uv tool install attackmap --with attackmap-analyzer-swift \
63
+ --index mlaify=https://pkg.mlaify.io/pypi/simple/ --index-strategy first-index
64
+ # or from source:
65
+ pip install git+https://github.com/mlaify/attackmap-analyzer-swift.git # alongside attackmap
66
+ ```
67
+
68
+ The analyzer is **experimental and opt-in** (`enabled_by_default=False`): installing
69
+ it does not make it run on every scan. Select it explicitly:
70
+
71
+ ```bash
72
+ attackmap analyze /path/to/swift/project -m swift
73
+ ```
74
+
75
+ Files are walked with `attackmap.sdk.iter_repo_files`: `.build/`, `DerivedData/`,
76
+ `Pods/`, `Carthage/` and AttackMap's shared skip list (`build/`, `.git/`,
77
+ `node_modules/`, ...) are pruned by their name *inside* the repo, and symlinks out
78
+ of the repo are not followed.
79
+
80
+ ## What it does today
81
+
82
+ - **SwiftPM detection** — recognizes `Package.swift`, `.xcodeproj` / `.xcworkspace`,
83
+ and `.swift` sources; emits a `SwiftPM` framework hint.
84
+ - **Server-side routes** ([AttackMap#187](https://github.com/mlaify/AttackMap/issues/187)) —
85
+ Vapor 4 and Hummingbird 1.x/2.x route registrations (`app.get("users", ":id")`,
86
+ `on(.POST, ...)`, `webSocket`/`ws`, `router.get("/x")`, chained
87
+ `group.get(use:).post(use:)`), with prefixes from `grouped(...)` / `group(...)`
88
+ (closures, bindings and chains), and from where a `RouteCollection` or controller
89
+ is mounted (`register(collection:)`, `addRoutes(to:)`, `addRoutes(_:atPath:)`) when
90
+ every mount in the same Swift package agrees. Each route carries `auth`, `guards`
91
+ and `guard_evidence` (AttackMap#256): `required` when Vapor's
92
+ `X.guardMiddleware()` / `X.redirectMiddleware(...)`, Hummingbird's
93
+ `IsAuthenticatedMiddleware` or an `*Authorization*Middleware` applies, or the
94
+ handler calls `req.auth.require(...)` / `context.requireIdentity()` or guards on
95
+ the caller's identity with a 401. An authenticator alone (`UserAuthenticator()`,
96
+ `SessionAuthenticator`) leaves the route `unknown`, since it only identifies callers
97
+ who send credentials. Role checks in the handler (`guard user.roles.contains(.admin)`)
98
+ are reported as guards. Paths the code computes (`SiteURL.faq.pathComponents`) are
99
+ kept as `$(SiteURL.faq)`, and an unknown mount as `$(TypeName)`. Test targets
100
+ (`Tests/`, `*Tests/`) are skipped.
101
+ - **iOS/macOS app attack surface** ([AttackMap#188](https://github.com/mlaify/AttackMap/issues/188)) —
102
+ reported as `AppSurfaceIssue`s, which core turns into `apple-*` findings with CWE/OWASP ids.
103
+ - `Info.plist` (XML or binary, read with `plistlib`, never executed): App Transport
104
+ Security exceptions, i.e. `NSAllowsArbitraryLoads` (medium; low when a scoped key makes
105
+ iOS 10+ ignore it), `...InWebContent` / `...ForMedia`, and per-domain
106
+ `NSExceptionAllowsInsecureHTTPLoads` or a TLS minimum below 1.2 (low). Each custom
107
+ `CFBundleURLSchemes` scheme is an entry point and an `apple-custom-url-scheme` finding
108
+ (low); the handlers (`application(_:open:options:)`,
109
+ `scene(_:openURLContexts:)`, `.onOpenURL`) are reported as entry points.
110
+ - `*.entitlements`: `cs.disable-library-validation`, `allow-dyld-environment-variables`,
111
+ `allow-unsigned-executable-memory`, `disable-executable-page-protection` (medium);
112
+ `get-task-allow` outside a Debug/Dev entitlements file (low); `applinks:` universal-link
113
+ domains as entry points.
114
+ - `*.xcconfig`: secret-named build settings (`API_KEY`, `CLIENT_SECRET`, `AUTH_TOKEN`)
115
+ with a literal value (not `$(...)`, not a placeholder), reported as hard-coded secrets
116
+ with the value masked.
117
+ - Swift code:
118
+ - `evaluateJavaScript("...'\(value)'...")` where a value is interpolated *inside a JS
119
+ string literal* and isn't visibly encoded inline or where it was bound (`sanitized`,
120
+ `escaped`, `json`, ...). Medium when the value is named like input, else low.
121
+ - Secret-named values or keys written to `UserDefaults` (`set(_:forKey:)`,
122
+ `@AppStorage("authToken")`), or copied to `UIPasteboard.general`.
123
+ - `NSXPCListener` services (entry points), and a `shouldAcceptNewConnection` that
124
+ returns `true` without checking the peer: no audit token / PID / code-signing
125
+ requirement, no validate/verify helper, and the connection isn't passed to any call.
126
+ High when a `NSXPCListener(machServiceName:)` is registered in the same file or
127
+ target (top-level directory); otherwise low, since a bundled XPC service or anonymous
128
+ listener is reachable only by its parent app or an endpoint holder.
129
+ - `NSClassFromString` / `NSSelectorFromString` / `dlopen` on a string built at the
130
+ call site.
131
+ - URL-scheme and universal-link handlers as entry points.
132
+ - **Dependency SBOM** — parses `Package.resolved` (formats **v1**, **v2**, and **v3**,
133
+ including the copy Xcode stashes under `*.xcworkspace/xcshareddata/swiftpm/`) into
134
+ the scan's dependency inventory, marking direct vs. transitive by cross-referencing
135
+ the `Package.swift` manifest. SwiftPM pins carry their repository URL, which
136
+ `--cve` matches against OSV's `SwiftURL` ecosystem. CocoaPods `Podfile.lock` pods
137
+ are inventoried (`cocoapods`; direct when listed under `DEPENDENCIES:`, subspecs
138
+ folded into their pod); OSV has no CocoaPods database, so they aren't CVE-matched.
139
+ Carthage `Cartfile.resolved` pins are inventoried as `carthage` (direct when the
140
+ `Cartfile` lists them, a leading `v` dropped from tag versions) with the repository URL,
141
+ so `--cve` matches them against OSV's `SwiftURL` ecosystem like SwiftPM pins.
142
+ - **Framework hints** — flags server-side frameworks (Vapor, Hummingbird, Kitura,
143
+ Perfect) when declared in the manifest.
144
+ - **Entrypoints** — `main.swift` and `@main`.
145
+
146
+ - **Objective-C / Objective-C++** ([AttackMap#190](https://github.com/mlaify/AttackMap/issues/190)) —
147
+ a second analyzer in this package, `objc` (opt-in: `-m objc`), for `.m`/`.mm` files that
148
+ look like Objective-C (not MATLAB). It runs the same project-file checks (Info.plist,
149
+ entitlements, xcconfig, Cartfile.resolved) and, in code: URL-scheme / universal-link / XPC
150
+ entry points, `evaluateJavaScript:` with a `stringWithFormat:` `%@` inside a JS string
151
+ literal, secret-named values or keys in `NSUserDefaults` or on the pasteboard, XPC
152
+ listeners that return `YES` without checking the peer, `NSClassFromString` /
153
+ `NSSelectorFromString` / `dlopen` on built strings, and the native weaknesses shared with
154
+ the C/C++ plugins: a non-literal, non-localized format with no arguments
155
+ (`[NSString stringWithFormat:msg]`, `NSLog(msg)`) as `format_string`, and `/bin/sh -c`
156
+ with a built command or `system()`/`popen()` on a non-literal as `native_command_exec`.
157
+ `NSFileManager`/`NSURL` file sinks aren't reported (no Objective-C taint pass yet).
158
+ When both analyzers run, core's merge drops the duplicate project-file signals.
159
+
160
+ ## Roadmap
161
+
162
+ - Taint for Swift/Objective-C sources (URL-scheme parameters to WKWebView / file sinks).
163
+
164
+ Part of the [Swift analyzer epic](https://github.com/mlaify/AttackMap/issues/204).
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ pip install -e ".[dev]"
170
+ pytest -q
171
+ ```
172
+
173
+ ## License
174
+
175
+ [MIT](LICENSE). Copyright (c) 2026 Matthew Davis and AttackMap Contributors.
@@ -0,0 +1,13 @@
1
+ attackmap_analyzer_swift/__init__.py,sha256=gUAWMW11wZcslK-XzBfwzddIEFlWSODewiZNrxgPRoQ,112
2
+ attackmap_analyzer_swift/analyzer.py,sha256=bUXmKQiAFOwcpAWzpLu4EvJ_xk0dj8Cdk8OdNGxjArI,12595
3
+ attackmap_analyzer_swift/apple_surface.py,sha256=4lZ9oAF4PpMWKxAWkEcxy9djTa4y8oUDoOoZBoeIGpk,32041
4
+ attackmap_analyzer_swift/objc.py,sha256=ZIpQazCaiZbSAHQ1wgmxz9RNwVsMgc6vNksOuocXI9c,22134
5
+ attackmap_analyzer_swift/routes.py,sha256=uBFbKTtm7WYCsp5FS1vuqGVcrsP6H2EeAwrL170i9SA,51659
6
+ attackmap_analyzer_swift/sbom.py,sha256=104s2rzvolkSMf5d_7NBkPag5WftxfTf-pAsz71horI,1939
7
+ attackmap_analyzer_swift/swiftsrc.py,sha256=r_PEIg27qnSCfQxZib7XwUYsY_nW86aBZVdhzr9qXco,7964
8
+ attackmap_analyzer_swift-0.2.0.dist-info/licenses/LICENSE,sha256=vtPQVGUyxq359j0JmD3_eJaXyHmaHyjzaMOtaLwJc88,1097
9
+ attackmap_analyzer_swift-0.2.0.dist-info/METADATA,sha256=ly99GJ5RKdXfvyWvuunYbF8f9nYS6L3KEehaEr0PeLM,10027
10
+ attackmap_analyzer_swift-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
11
+ attackmap_analyzer_swift-0.2.0.dist-info/entry_points.txt,sha256=e_-1ufbXrCrWOFAD3aldobb7-EPOM9WKHy3eWcTKEp0,114
12
+ attackmap_analyzer_swift-0.2.0.dist-info/top_level.txt,sha256=zEpqb9TzFle_vNrDOOdgaAnLxKCsWflwn-qx7-SZJR0,25
13
+ attackmap_analyzer_swift-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [attackmap.analyzers]
2
+ objc = attackmap_analyzer_swift:ObjCAnalyzer
3
+ swift = attackmap_analyzer_swift:SwiftAnalyzer
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Davis and AttackMap 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
+ attackmap_analyzer_swift