attackmap 0.4.1__tar.gz → 0.4.2__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.
- {attackmap-0.4.1/src/attackmap.egg-info → attackmap-0.4.2}/PKG-INFO +1 -1
- {attackmap-0.4.1 → attackmap-0.4.2}/pyproject.toml +1 -1
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/__init__.py +1 -1
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/anomalies.py +22 -2
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/cli.py +48 -29
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/scanner.py +3 -4
- {attackmap-0.4.1 → attackmap-0.4.2/src/attackmap.egg-info}/PKG-INFO +1 -1
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_anomalies.py +26 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/LICENSE +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/README.md +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/setup.cfg +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/authz.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/cve.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/exploitability.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/llm_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/merge.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/report.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/review_prompts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/sarif.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/sbom.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/taint.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/threat_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap.egg-info/SOURCES.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap.egg-info/requires.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_authz.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_config_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_crypto.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_cve.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_diff.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_graph.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_hunt.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_llm_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_merge.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_progress.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_provenance.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_remediate.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_report.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_review_json.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_sarif.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_sbom.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_suggest.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_taint.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_topology.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.2}/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.2
|
|
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.2"
|
|
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.2"
|
|
@@ -36,10 +36,16 @@ from __future__ import annotations
|
|
|
36
36
|
import re
|
|
37
37
|
from dataclasses import dataclass
|
|
38
38
|
from pathlib import Path
|
|
39
|
+
from typing import TYPE_CHECKING
|
|
39
40
|
|
|
40
41
|
from .models import Anomaly, Route, ScanResult
|
|
41
42
|
from .srcpaths import is_test_file
|
|
42
43
|
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .progress import JsonScanProgress, ScanProgress
|
|
46
|
+
|
|
47
|
+
_Progress = ScanProgress | JsonScanProgress
|
|
48
|
+
|
|
43
49
|
|
|
44
50
|
@dataclass
|
|
45
51
|
class _SignalCtx:
|
|
@@ -106,8 +112,17 @@ _MIN_GROUP = 3
|
|
|
106
112
|
_SCAFFOLD_RE = re.compile(r"^(?:api|rest|graphql|gql|v\d+(?:beta\d*|alpha\d*)?)$", re.IGNORECASE)
|
|
107
113
|
|
|
108
114
|
|
|
109
|
-
def find_anomalies(
|
|
110
|
-
|
|
115
|
+
def find_anomalies(
|
|
116
|
+
scan: ScanResult,
|
|
117
|
+
root: str | Path | None = None,
|
|
118
|
+
progress: "_Progress | None" = None,
|
|
119
|
+
) -> list[Anomaly]:
|
|
120
|
+
"""Return within-repo consistency outliers for the routes in ``scan``.
|
|
121
|
+
|
|
122
|
+
On a large route surface the per-cohort source-window reads dominate this
|
|
123
|
+
pass, so when a ``progress`` reporter is supplied it drives a determinate
|
|
124
|
+
bar over the cohorts (rather than an open-ended spinner).
|
|
125
|
+
"""
|
|
111
126
|
root_path = Path(root or scan.root).resolve()
|
|
112
127
|
|
|
113
128
|
routes = [r for r in scan.routes if not is_test_file(r.file)]
|
|
@@ -129,8 +144,13 @@ def find_anomalies(scan: ScanResult, root: str | Path | None = None) -> list[Ano
|
|
|
129
144
|
|
|
130
145
|
ctx = _SignalCtx(root_path, line_cache, file_route_lines)
|
|
131
146
|
|
|
147
|
+
if progress is not None:
|
|
148
|
+
progress.begin(len(groups), "Anomaly / outlier detection")
|
|
149
|
+
|
|
132
150
|
out: list[Anomaly] = []
|
|
133
151
|
for key, members in groups.items():
|
|
152
|
+
if progress is not None:
|
|
153
|
+
progress.advance(key)
|
|
134
154
|
if len(members) < _MIN_GROUP:
|
|
135
155
|
continue
|
|
136
156
|
if not _is_route_like_cohort(key, members):
|
|
@@ -160,7 +160,11 @@ def analyze(
|
|
|
160
160
|
scan = analyze_repository(repo_path, analyzers=active_analyzers, progress=scan_progress)
|
|
161
161
|
if cve and scan.dependencies:
|
|
162
162
|
typer.echo(f"Checking {len(scan.dependencies)} dependencies against OSV.dev…")
|
|
163
|
-
|
|
163
|
+
scan_progress.stage(f"Checking {len(scan.dependencies)} dependencies against OSV.dev")
|
|
164
|
+
try:
|
|
165
|
+
vulns, cve_summary = query_vulnerabilities(scan.dependencies)
|
|
166
|
+
finally:
|
|
167
|
+
scan_progress.done()
|
|
164
168
|
scan.vulnerabilities = vulns
|
|
165
169
|
typer.echo(
|
|
166
170
|
f"CVE lookup: {len(vulns)} vulnerabilit"
|
|
@@ -257,15 +261,19 @@ def analyze(
|
|
|
257
261
|
typer.echo(
|
|
258
262
|
f"Generating narrative review via Claude (backend={llm_backend}, may take a minute)..."
|
|
259
263
|
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
264
|
+
scan_progress.stage(f"Claude is writing the defensive review (backend={llm_backend})")
|
|
265
|
+
try:
|
|
266
|
+
result = generate_llm_review(
|
|
267
|
+
scan,
|
|
268
|
+
attack_surfaces,
|
|
269
|
+
findings,
|
|
270
|
+
attack_paths,
|
|
271
|
+
model=llm_model,
|
|
272
|
+
effort=effort_value, # type: ignore[arg-type]
|
|
273
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
274
|
+
)
|
|
275
|
+
finally:
|
|
276
|
+
scan_progress.done()
|
|
269
277
|
except LlmReviewError as exc:
|
|
270
278
|
typer.echo(f"LLM review skipped: {exc}", err=True)
|
|
271
279
|
else:
|
|
@@ -307,16 +315,23 @@ def analyze(
|
|
|
307
315
|
f"Hunting for vulnerability hypotheses via Claude "
|
|
308
316
|
f"({'adjudicated against source, ' if verify else ''}backend={llm_backend}, may take a minute)..."
|
|
309
317
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
findings,
|
|
314
|
-
attack_paths,
|
|
315
|
-
model=llm_model,
|
|
316
|
-
effort=hunt_effort_value, # type: ignore[arg-type]
|
|
317
|
-
backend=llm_backend, # type: ignore[arg-type]
|
|
318
|
-
mode=hunt_mode, # type: ignore[arg-type]
|
|
318
|
+
scan_progress.stage(
|
|
319
|
+
f"Claude is hunting exploit-chain hypotheses"
|
|
320
|
+
f"{' + verifying against source' if verify else ''} (backend={llm_backend})"
|
|
319
321
|
)
|
|
322
|
+
try:
|
|
323
|
+
hunt_result = generate_llm_review(
|
|
324
|
+
scan,
|
|
325
|
+
attack_surfaces,
|
|
326
|
+
findings,
|
|
327
|
+
attack_paths,
|
|
328
|
+
model=llm_model,
|
|
329
|
+
effort=hunt_effort_value, # type: ignore[arg-type]
|
|
330
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
331
|
+
mode=hunt_mode, # type: ignore[arg-type]
|
|
332
|
+
)
|
|
333
|
+
finally:
|
|
334
|
+
scan_progress.done()
|
|
320
335
|
except LlmReviewError as exc:
|
|
321
336
|
typer.echo(f"Vulnerability hunt skipped: {exc}", err=True)
|
|
322
337
|
else:
|
|
@@ -359,16 +374,20 @@ def analyze(
|
|
|
359
374
|
typer.echo(
|
|
360
375
|
f"Generating remediation suggestions via Claude (backend={llm_backend}, may take a minute)..."
|
|
361
376
|
)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
377
|
+
scan_progress.stage(f"Claude is drafting remediation suggestions (backend={llm_backend})")
|
|
378
|
+
try:
|
|
379
|
+
rem_result = generate_llm_review(
|
|
380
|
+
scan,
|
|
381
|
+
attack_surfaces,
|
|
382
|
+
findings,
|
|
383
|
+
attack_paths,
|
|
384
|
+
model=llm_model,
|
|
385
|
+
effort=rem_effort_value, # type: ignore[arg-type]
|
|
386
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
387
|
+
mode="remediate",
|
|
388
|
+
)
|
|
389
|
+
finally:
|
|
390
|
+
scan_progress.done()
|
|
372
391
|
except LlmReviewError as exc:
|
|
373
392
|
typer.echo(f"Remediation skipped: {exc}", err=True)
|
|
374
393
|
else:
|
|
@@ -736,10 +736,9 @@ def scan_repo(
|
|
|
736
736
|
progress.stage("Authorization (BOLA/IDOR)")
|
|
737
737
|
result.authz_candidates = analyze_authz(result, root_path)
|
|
738
738
|
# Anomaly / outlier pass (#78): the odd-one-out among sibling routes.
|
|
739
|
-
# Runs last so the full route list is assembled into cohorts.
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
result.anomalies = find_anomalies(result, root_path)
|
|
739
|
+
# Runs last so the full route list is assembled into cohorts. It drives its
|
|
740
|
+
# own determinate per-cohort progress (the slow tail on big route surfaces).
|
|
741
|
+
result.anomalies = find_anomalies(result, root_path, progress=progress)
|
|
743
742
|
if progress is not None:
|
|
744
743
|
progress.done()
|
|
745
744
|
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attackmap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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>
|
|
@@ -369,3 +369,29 @@ def test_auth_outlier_becomes_tagged_finding(tmp_path: Path) -> None:
|
|
|
369
369
|
assert auth.severity == "high"
|
|
370
370
|
assert auth.attack_techniques
|
|
371
371
|
assert any("/api/users/export" in e for e in auth.evidence)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_progress_reported_per_cohort(tmp_path: Path) -> None:
|
|
375
|
+
"""find_anomalies drives a determinate begin/advance over cohorts (#status)."""
|
|
376
|
+
import io
|
|
377
|
+
import json
|
|
378
|
+
|
|
379
|
+
from attackmap.progress import JsonScanProgress
|
|
380
|
+
|
|
381
|
+
scan = _scan_with_routes(
|
|
382
|
+
tmp_path,
|
|
383
|
+
"views.py",
|
|
384
|
+
_AUTH_COHORT,
|
|
385
|
+
[
|
|
386
|
+
("/api/users", "GET", '"/api/users")'),
|
|
387
|
+
("/api/users/<id>", "GET", '"/api/users/<id>")'),
|
|
388
|
+
("/api/users/export", "GET", '"/api/users/export")'),
|
|
389
|
+
],
|
|
390
|
+
)
|
|
391
|
+
stream = io.StringIO()
|
|
392
|
+
find_anomalies(scan, progress=JsonScanProgress(stream=stream, min_interval=0.0))
|
|
393
|
+
events = [json.loads(line) for line in stream.getvalue().splitlines() if line.strip()]
|
|
394
|
+
begin = next(e for e in events if e["event"] == "begin")
|
|
395
|
+
assert begin["label"] == "Anomaly / outlier detection"
|
|
396
|
+
assert begin["total"] >= 1
|
|
397
|
+
assert any(e["event"] == "advance" for e in events)
|
|
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
|