attackmap 0.4.4__tar.gz → 0.4.5__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.
Potentially problematic release.
This version of attackmap might be problematic. Click here for more details.
- {attackmap-0.4.4/src/attackmap.egg-info → attackmap-0.4.5}/PKG-INFO +1 -1
- {attackmap-0.4.4 → attackmap-0.4.5}/pyproject.toml +1 -1
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/__init__.py +1 -1
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/config_scanner.py +46 -5
- {attackmap-0.4.4 → attackmap-0.4.5/src/attackmap.egg-info}/PKG-INFO +1 -1
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_config_scanner.py +40 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/LICENSE +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/README.md +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/setup.cfg +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/anomalies.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/authz.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/cli.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/cve.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/exploitability.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/llm_review.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/merge.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/models.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/report.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/review_prompts.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/sarif.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/sbom.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/scanner.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/taint.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/threat_model.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap.egg-info/SOURCES.txt +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap.egg-info/requires.txt +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_anomalies.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_authz.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_crypto.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_cve.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_diff.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_graph.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_hunt.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_llm_review.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_merge.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_modules_cli.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_progress.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_provenance.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_remediate.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_report.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_review_json.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_sarif.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_sbom.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_scanner.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_suggest.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_taint.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_topology.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.4 → attackmap-0.4.5}/tests/test_webhardening.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attackmap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: AI-assisted defensive security analyzer for codebases — scans a repository, models assets and controls, finds cross-cutting weaknesses, and generates an evidence-grounded review with MITRE ATT&CK mappings and detection-engineering hints.
|
|
5
5
|
Author: AttackMap Contributors
|
|
6
6
|
Author-email: Matthew Davis <matthewd@matthewd.xyz>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "attackmap"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.5"
|
|
8
8
|
description = "AI-assisted defensive security analyzer for codebases — scans a repository, models assets and controls, finds cross-cutting weaknesses, and generates an evidence-grounded review with MITRE ATT&CK mappings and detection-engineering hints."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.4.
|
|
2
|
+
__version__ = "0.4.5"
|
|
@@ -15,11 +15,45 @@ want structural analysis can extend on top; this layer stays regex-only.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import bisect
|
|
18
19
|
import re
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
|
|
21
22
|
from .models import DatabaseHint, ExternalCall, ScanResult, SecretHint
|
|
22
|
-
from .scanner import
|
|
23
|
+
from .scanner import _line_snippet
|
|
24
|
+
|
|
25
|
+
# Config files larger than this are skipped — at that size they're invariably
|
|
26
|
+
# generated (lock files, minified bundles, or AttackMap's own JSON reports)
|
|
27
|
+
# rather than hand-written config, and scanning multi-megabyte text for URL /
|
|
28
|
+
# secret regexes is pointlessly slow.
|
|
29
|
+
_MAX_CONFIG_BYTES = 5_000_000
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _LineIndex:
|
|
33
|
+
"""O(log n) line-number lookups for a file's text.
|
|
34
|
+
|
|
35
|
+
`scanner._line_of` counts newlines from the start on every call — O(n) each,
|
|
36
|
+
which degrades to O(n^2) when a large file yields many matches (the cause of
|
|
37
|
+
a scan hang on multi-megabyte JSON). Precomputing the line-start offsets once
|
|
38
|
+
and binary-searching keeps extraction linear.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
__slots__ = ("_starts",)
|
|
42
|
+
|
|
43
|
+
def __init__(self, content: str) -> None:
|
|
44
|
+
starts = [0]
|
|
45
|
+
find = content.find
|
|
46
|
+
idx = find("\n")
|
|
47
|
+
while idx != -1:
|
|
48
|
+
starts.append(idx + 1)
|
|
49
|
+
idx = find("\n", idx + 1)
|
|
50
|
+
self._starts = starts
|
|
51
|
+
|
|
52
|
+
def line_of(self, offset: int) -> int:
|
|
53
|
+
"""1-indexed line number for a character offset."""
|
|
54
|
+
if offset <= 0:
|
|
55
|
+
return 1
|
|
56
|
+
return bisect.bisect_right(self._starts, offset)
|
|
23
57
|
|
|
24
58
|
|
|
25
59
|
CONFIG_SUFFIXES = {".yaml", ".yml", ".toml", ".json", ".ini", ".cfg", ".env"}
|
|
@@ -48,6 +82,8 @@ CONFIG_EXCLUDE_FILENAMES = frozenset({
|
|
|
48
82
|
_SKIP_DIRS = frozenset({
|
|
49
83
|
"node_modules",
|
|
50
84
|
".git",
|
|
85
|
+
".attackmap-gui", # the macOS GUI's report output dir — never scan our own output
|
|
86
|
+
".attackmap", # CLI cache / output dir
|
|
51
87
|
".venv",
|
|
52
88
|
"venv",
|
|
53
89
|
"dist",
|
|
@@ -183,6 +219,8 @@ def scan_config_repo(root: str | Path) -> ScanResult:
|
|
|
183
219
|
if not should_scan_config_file(path):
|
|
184
220
|
continue
|
|
185
221
|
try:
|
|
222
|
+
if path.stat().st_size > _MAX_CONFIG_BYTES:
|
|
223
|
+
continue
|
|
186
224
|
content = path.read_text(encoding="utf-8")
|
|
187
225
|
except (UnicodeDecodeError, OSError):
|
|
188
226
|
continue
|
|
@@ -196,6 +234,7 @@ def scan_config_repo(root: str | Path) -> ScanResult:
|
|
|
196
234
|
|
|
197
235
|
def _extract_db_urls(content: str, relative: str, result: ScanResult) -> None:
|
|
198
236
|
seen: set[str] = {(h.kind, h.file) for h in result.databases if h.file == relative}
|
|
237
|
+
index = _LineIndex(content)
|
|
199
238
|
for match in _DB_URL_PATTERN.finditer(content):
|
|
200
239
|
scheme = match.group("scheme").lower()
|
|
201
240
|
kind = _DB_SCHEME_TO_KIND.get(scheme, scheme)
|
|
@@ -205,7 +244,7 @@ def _extract_db_urls(content: str, relative: str, result: ScanResult) -> None:
|
|
|
205
244
|
DatabaseHint(
|
|
206
245
|
kind=kind,
|
|
207
246
|
file=relative,
|
|
208
|
-
line=
|
|
247
|
+
line=index.line_of(match.start()),
|
|
209
248
|
evidence_text=_line_snippet(content, match.start()),
|
|
210
249
|
)
|
|
211
250
|
)
|
|
@@ -221,7 +260,7 @@ def _extract_db_urls(content: str, relative: str, result: ScanResult) -> None:
|
|
|
221
260
|
SecretHint(
|
|
222
261
|
name=_redact(password),
|
|
223
262
|
file=relative,
|
|
224
|
-
line=
|
|
263
|
+
line=index.line_of(match.start()),
|
|
225
264
|
evidence_text=_line_snippet(content, match.start()),
|
|
226
265
|
confidence=1.0,
|
|
227
266
|
kind=f"{kind}_url_password",
|
|
@@ -231,6 +270,7 @@ def _extract_db_urls(content: str, relative: str, result: ScanResult) -> None:
|
|
|
231
270
|
|
|
232
271
|
def _extract_http_targets(content: str, relative: str, result: ScanResult) -> None:
|
|
233
272
|
seen_targets = {(c.target, c.file) for c in result.external_calls if c.file == relative}
|
|
273
|
+
index = _LineIndex(content)
|
|
234
274
|
for match in _HTTP_URL_PATTERN.finditer(content):
|
|
235
275
|
target = match.group(0)
|
|
236
276
|
# Trim trailing punctuation that looks like URL end
|
|
@@ -244,7 +284,7 @@ def _extract_http_targets(content: str, relative: str, result: ScanResult) -> No
|
|
|
244
284
|
ExternalCall(
|
|
245
285
|
target=target,
|
|
246
286
|
file=relative,
|
|
247
|
-
line=
|
|
287
|
+
line=index.line_of(match.start()),
|
|
248
288
|
evidence_text=_line_snippet(content, match.start()),
|
|
249
289
|
)
|
|
250
290
|
)
|
|
@@ -258,12 +298,13 @@ def _extract_host(url: str) -> str | None:
|
|
|
258
298
|
|
|
259
299
|
def _extract_secret_kvs(content: str, relative: str, result: ScanResult) -> None:
|
|
260
300
|
seen: set[tuple[str, int]] = {(h.name, h.line or 0) for h in result.secret_hints if h.file == relative}
|
|
301
|
+
index = _LineIndex(content)
|
|
261
302
|
for match in _SECRET_KEY_RE.finditer(content):
|
|
262
303
|
key = match.group("key")
|
|
263
304
|
value = match.group("value") or match.group("bare") or ""
|
|
264
305
|
if not value or _is_placeholder(value):
|
|
265
306
|
continue
|
|
266
|
-
line =
|
|
307
|
+
line = index.line_of(match.start())
|
|
267
308
|
# Store `key` as the SecretHint.name so consumers see WHICH secret
|
|
268
309
|
# was leaked (auth vs stripe vs db). Value itself is redacted into
|
|
269
310
|
# evidence_text via the same helper hardcoded-secret detection uses.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attackmap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: AI-assisted defensive security analyzer for codebases — scans a repository, models assets and controls, finds cross-cutting weaknesses, and generates an evidence-grounded review with MITRE ATT&CK mappings and detection-engineering hints.
|
|
5
5
|
Author: AttackMap Contributors
|
|
6
6
|
Author-email: Matthew Davis <matthewd@matthewd.xyz>
|
|
@@ -263,3 +263,43 @@ def test_analyze_repository_preserves_provenance_on_config_signals(tmp_path: Pat
|
|
|
263
263
|
config_dbs = [d for d in result.databases if d.file == "app.yaml"]
|
|
264
264
|
assert config_dbs
|
|
265
265
|
assert config_dbs[0].source_analyzer == "config"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
# Large-file / feedback-loop hang regression (config_scanner _line_of O(n^2))
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
import attackmap.config_scanner as _cfg
|
|
273
|
+
from attackmap.config_scanner import _LineIndex
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_line_index_matches_naive_line_numbers() -> None:
|
|
277
|
+
content = "a\nbb\n\nccc\nd"
|
|
278
|
+
expected = {0: 1, 1: 1, 2: 2, 4: 2, 5: 3, 6: 4, 9: 4, 10: 5}
|
|
279
|
+
index = _LineIndex(content)
|
|
280
|
+
for offset, line in expected.items():
|
|
281
|
+
assert index.line_of(offset) == line, (offset, index.line_of(offset))
|
|
282
|
+
assert index.line_of(-5) == 1
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_skips_attackmap_output_dir(tmp_path: Path) -> None:
|
|
286
|
+
# AttackMap writes reports into .attackmap-gui/ inside scanned repos; scanning
|
|
287
|
+
# them back is a feedback loop that hung on the multi-MB report (#gui-hang).
|
|
288
|
+
(tmp_path / "app.json").write_text('{"api": "https://api.real-service.com/v1"}')
|
|
289
|
+
reports = tmp_path / ".attackmap-gui" / "reports"
|
|
290
|
+
reports.mkdir(parents=True)
|
|
291
|
+
(reports / "attackmap-report.json").write_text('{"u": "https://leaked.internal-host.net/x"}')
|
|
292
|
+
|
|
293
|
+
files = {c.file for c in scan_config_repo(tmp_path).external_calls}
|
|
294
|
+
assert not any(".attackmap-gui" in f for f in files)
|
|
295
|
+
assert any("app.json" in f for f in files)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_skips_oversized_config_file(tmp_path: Path, monkeypatch) -> None:
|
|
299
|
+
monkeypatch.setattr(_cfg, "_MAX_CONFIG_BYTES", 200)
|
|
300
|
+
(tmp_path / "big.json").write_text('{"u":"https://api.real-service.com/"}' + " " * 500)
|
|
301
|
+
(tmp_path / "small.json").write_text('{"u":"https://api.real-service.com/"}')
|
|
302
|
+
|
|
303
|
+
files = {c.file for c in scan_config_repo(tmp_path).external_calls}
|
|
304
|
+
assert "big.json" not in files
|
|
305
|
+
assert "small.json" in files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|