adaptersentry 2.0.1__tar.gz → 2.0.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.
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/PKG-INFO +2 -2
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/README.md +1 -1
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/PKG-INFO +2 -2
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/pyproject.toml +1 -1
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/detectors/intra_adapter.py +22 -4
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/parsers/names.py +11 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/scanner.py +18 -4
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/scoring/verdict.py +4 -1
- adaptersentry-2.0.2/src/adaptersentry/version.py +1 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/tests/test_scanner.py +8 -0
- adaptersentry-2.0.1/src/adaptersentry/version.py +0 -1
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/LICENSE +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/SOURCES.txt +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/dependency_links.txt +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/entry_points.txt +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/requires.txt +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/adaptersentry.egg-info/top_level.txt +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/check_regression.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/corpus.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/harness.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/hub_scanner.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/report.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/benchmarks/run_real.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/setup.cfg +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/__main__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/cli/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/cli/batch.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/cli/main.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/cli/scan.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/detectors/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/detectors/structural.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/cache.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/identity.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/manifest.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/orchestrator.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/orchestrator_ray.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/result_sink.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/cache.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/combined_report.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/identity.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/requests.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/worker.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/features/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/features/lowrank_core.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/features/spectral.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/parsers/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/parsers/adapter_config.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/parsers/adapter_file.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/parsers/safetensors.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporters/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporters/json.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporters/sarif.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporters/text.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporting/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/reporting/sanitize.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/schemas/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/schemas/errors.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/schemas/result.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/schemas/severity.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/scoring/__init__.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/tests/test_bench.py +0 -0
- {adaptersentry-2.0.1 → adaptersentry-2.0.2}/tests/test_harness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adaptersentry
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Static security scanner for LoRA adapters — detects anomalous weight patterns in .safetensors files
|
|
5
5
|
License:
|
|
6
6
|
Apache License
|
|
@@ -246,7 +246,7 @@ Dynamic: license-file
|
|
|
246
246
|
|
|
247
247
|

|
|
248
248
|

|
|
249
|
-

|
|
250
250
|
|
|
251
251
|
AdapterSentry is a static security scanner for LoRA adapters distributed as `.safetensors`
|
|
252
252
|
files. A LoRA adapter is a small file that changes how a large language model behaves; anyone
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
AdapterSentry is a static security scanner for LoRA adapters distributed as `.safetensors`
|
|
8
8
|
files. A LoRA adapter is a small file that changes how a large language model behaves; anyone
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adaptersentry
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Static security scanner for LoRA adapters — detects anomalous weight patterns in .safetensors files
|
|
5
5
|
License:
|
|
6
6
|
Apache License
|
|
@@ -246,7 +246,7 @@ Dynamic: license-file
|
|
|
246
246
|
|
|
247
247
|

|
|
248
248
|

|
|
249
|
-

|
|
250
250
|
|
|
251
251
|
AdapterSentry is a static security scanner for LoRA adapters distributed as `.safetensors`
|
|
252
252
|
files. A LoRA adapter is a small file that changes how a large language model behaves; anyone
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "adaptersentry"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.2"
|
|
8
8
|
description = "Static security scanner for LoRA adapters — detects anomalous weight patterns in .safetensors files"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
No reference profile is needed. A 70B adapter has ~80 modules of each kind
|
|
4
4
|
(q_proj, down_proj, …); each module is compared with the rest of its family.
|
|
5
5
|
|
|
6
|
-
Per family (same module name) and per feature:
|
|
6
|
+
Per family (same module name in the same layer stack) and per feature:
|
|
7
7
|
1. if layer indices are known, remove the depth trend with a Theil–Sen line
|
|
8
8
|
(median of pairwise slopes; insensitive to the outliers we look for);
|
|
9
9
|
2. robust z of the residuals: (x − median) / (1.4826 · MAD), with the
|
|
@@ -50,6 +50,11 @@ from adaptersentry.schemas.result import (
|
|
|
50
50
|
OUTLIER_Z = 3.5
|
|
51
51
|
CONCENTRATION_REVIEW_Z = 14.0
|
|
52
52
|
CONCENTRATION_FEATURES: tuple[str, ...] = ("kurt_dw", "row_top8", "row_hoyer")
|
|
53
|
+
# On Hub adapters 28 of 32 concentration alerts sat in the first 3 or last 2 layers of
|
|
54
|
+
# the model, which work differently from the middle. Such alerts keep the verdict but
|
|
55
|
+
# say so; a per-family threshold for edge layers needs reference profiles.
|
|
56
|
+
EDGE_FIRST_LAYERS = 3
|
|
57
|
+
EDGE_LAST_LAYERS = 2
|
|
53
58
|
# Robust z from small families is unreliable: MAD from a handful of points is
|
|
54
59
|
# noisy, and detrending consumes degrees of freedom. On synthetic clean adapters
|
|
55
60
|
# the maximum |z| reached 12-70 with 6-12 modules per family, but stayed ≤ 6.6
|
|
@@ -85,6 +90,7 @@ class ModuleEntry:
|
|
|
85
90
|
expert: int | None
|
|
86
91
|
features: ModuleFeatures
|
|
87
92
|
energy: float # ‖ΔW‖²_F, for concentration across modules
|
|
93
|
+
stack: str = "" # layer stack (language model, vision tower, …); part of the family
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
@dataclass
|
|
@@ -121,6 +127,13 @@ def detrend(layers: np.ndarray, x: np.ndarray) -> np.ndarray:
|
|
|
121
127
|
return x - (intercept + slope * layers)
|
|
122
128
|
|
|
123
129
|
|
|
130
|
+
def is_edge_layer(layer: int | None, n_layers: int | None) -> bool:
|
|
131
|
+
"""True for the first EDGE_FIRST_LAYERS and last EDGE_LAST_LAYERS layers of a stack."""
|
|
132
|
+
if layer is None or n_layers is None:
|
|
133
|
+
return False
|
|
134
|
+
return layer < EDGE_FIRST_LAYERS or layer >= n_layers - EDGE_LAST_LAYERS
|
|
135
|
+
|
|
136
|
+
|
|
124
137
|
def concentration_z(zs: dict[str, float]) -> float | None:
|
|
125
138
|
"""Joint concentration z of one module: the smallest of the signed z-values of
|
|
126
139
|
CONCENTRATION_FEATURES, so it is high only when all three rise together.
|
|
@@ -154,9 +167,14 @@ def compare_within_adapter(entries: list[ModuleEntry], *, top_k: int = 10) -> In
|
|
|
154
167
|
Returns:
|
|
155
168
|
IntraResult, or None if no family has at least MIN_FAMILY modules.
|
|
156
169
|
"""
|
|
170
|
+
# A family is one module name within one layer stack: a vision tower's q_proj is
|
|
171
|
+
# not compared with the language model's q_proj. Names are qualified with the
|
|
172
|
+
# stack only when the adapter covers more than one.
|
|
173
|
+
qualify = len({e.stack for e in entries}) > 1
|
|
174
|
+
family_of = {e.index: (f"{e.stack}/{e.module}" if qualify and e.stack else e.module) for e in entries}
|
|
157
175
|
families: dict[str, list[ModuleEntry]] = defaultdict(list)
|
|
158
176
|
for e in entries:
|
|
159
|
-
families[e.
|
|
177
|
+
families[family_of[e.index]].append(e)
|
|
160
178
|
|
|
161
179
|
z_by_module: dict[int, dict[str, float]] = defaultdict(dict)
|
|
162
180
|
contributions: list[tuple[float, Contributor]] = []
|
|
@@ -203,7 +221,7 @@ def compare_within_adapter(entries: list[ModuleEntry], *, top_k: int = 10) -> In
|
|
|
203
221
|
}
|
|
204
222
|
by_index = {e.index: e for e in entries}
|
|
205
223
|
conc_spikes = [
|
|
206
|
-
DepthSpike(module_type=
|
|
224
|
+
DepthSpike(module_type=family_of[idx], layer=by_index[idx].layer, z=v)
|
|
207
225
|
for idx, v in sorted(concentration.items(), key=lambda kv: -kv[1])
|
|
208
226
|
if v >= CONCENTRATION_REVIEW_Z and by_index[idx].layer is not None
|
|
209
227
|
][:top_k]
|
|
@@ -212,7 +230,7 @@ def compare_within_adapter(entries: list[ModuleEntry], *, top_k: int = 10) -> In
|
|
|
212
230
|
e = by_index[idx]
|
|
213
231
|
if v > OUTLIER_Z and e.layer is not None and len(spikes) < top_k:
|
|
214
232
|
signed = max(z_by_module[idx].values(), key=abs)
|
|
215
|
-
spikes.append(DepthSpike(module_type=
|
|
233
|
+
spikes.append(DepthSpike(module_type=family_of[idx], layer=e.layer, z=float(signed)))
|
|
216
234
|
|
|
217
235
|
energies = np.array([e.energy for e in entries if e.energy > 0.0], dtype=np.float64)
|
|
218
236
|
attn = [e.features.log_rms for e in entries if e.kind == "attention" and e.features.log_rms is not None]
|
|
@@ -20,6 +20,7 @@ _EMB_B = re.compile(r"^(?P<base>.+)\.lora_embedding_B(?:\.[A-Za-z0-9_\-]+)?$")
|
|
|
20
20
|
_DORA = re.compile(r"^(?P<base>.+)\.lora_magnitude_vector(?:\.[A-Za-z0-9_\-]+)?(?:\.weight)?$")
|
|
21
21
|
_TRAINABLE_TOKENS = re.compile(r"^(?P<base>.+)\.trainable_tokens_(?:delta|original)(?:\.[A-Za-z0-9_\-]+)?$")
|
|
22
22
|
|
|
23
|
+
_PEFT_PREFIX = "base_model.model."
|
|
23
24
|
_LAYER = re.compile(r"(?:^|\.)(?:layers|h|blocks|layer)\.(\d+)(?:\.|$)")
|
|
24
25
|
_EXPERT = re.compile(r"(?:^|\.)experts\.(\d+)(?:\.|$)")
|
|
25
26
|
|
|
@@ -57,6 +58,10 @@ class ModuleLocation:
|
|
|
57
58
|
expert: int | None
|
|
58
59
|
module: str
|
|
59
60
|
kind: ModuleKind
|
|
61
|
+
# Path of the layer stack the module belongs to, e.g. "model.language_model" or
|
|
62
|
+
# "model.vision_tower.vision_model.encoder"; "" when the path has no layer index.
|
|
63
|
+
# Multimodal models reuse module names (q_proj, up_proj) across stacks.
|
|
64
|
+
stack: str = ""
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
def parse_key(key: str) -> ParsedKey:
|
|
@@ -98,9 +103,15 @@ def locate(base: str) -> ModuleLocation:
|
|
|
98
103
|
kind = "router"
|
|
99
104
|
else:
|
|
100
105
|
kind = "other"
|
|
106
|
+
stack = ""
|
|
107
|
+
if layer_m:
|
|
108
|
+
stack = base[:layer_m.start()]
|
|
109
|
+
if stack.startswith(_PEFT_PREFIX):
|
|
110
|
+
stack = stack[len(_PEFT_PREFIX):]
|
|
101
111
|
return ModuleLocation(
|
|
102
112
|
layer=int(layer_m.group(1)) if layer_m else None,
|
|
103
113
|
expert=int(expert_m.group(1)) if expert_m else None,
|
|
104
114
|
module=name,
|
|
105
115
|
kind=kind,
|
|
116
|
+
stack=stack,
|
|
106
117
|
)
|
|
@@ -42,6 +42,7 @@ from adaptersentry.detectors.intra_adapter import (
|
|
|
42
42
|
ModuleEntry,
|
|
43
43
|
compare_within_adapter,
|
|
44
44
|
concentration_z,
|
|
45
|
+
is_edge_layer,
|
|
45
46
|
)
|
|
46
47
|
from adaptersentry.detectors.structural import find_siblings, structural_findings
|
|
47
48
|
from adaptersentry.features.spectral import head_features, module_features
|
|
@@ -322,7 +323,8 @@ def _scan_inventory(path, inv: AdapterInventory, cfg_read, siblings, artifact, s
|
|
|
322
323
|
idx = len(records)
|
|
323
324
|
records.append(ModuleRecord(**base_record, scale=res.scale, status="ok", features=feats, heads=heads))
|
|
324
325
|
entries.append(ModuleEntry(index=idx, module=loc.module, kind=loc.kind, layer=loc.layer,
|
|
325
|
-
expert=loc.expert, features=feats, energy=core.fro ** 2
|
|
326
|
+
expert=loc.expert, features=feats, energy=core.fro ** 2,
|
|
327
|
+
stack=loc.stack))
|
|
326
328
|
del pd
|
|
327
329
|
|
|
328
330
|
intra = compare_within_adapter(entries)
|
|
@@ -335,19 +337,30 @@ def _scan_inventory(path, inv: AdapterInventory, cfg_read, siblings, artifact, s
|
|
|
335
337
|
struct = structural_findings(inv, config, cfg_read.problems, siblings,
|
|
336
338
|
unresolved_scale_pattern=unresolved_pattern)
|
|
337
339
|
findings = list(struct.findings)
|
|
340
|
+
top_spike_edge = False
|
|
338
341
|
if intra is not None:
|
|
339
342
|
pair_by_index = {i: inv.pairs[i] for i in range(len(inv.pairs))}
|
|
343
|
+
stack_depth: dict[str, int] = {}
|
|
344
|
+
for p in inv.pairs:
|
|
345
|
+
if p.location.layer is not None:
|
|
346
|
+
s = p.location.stack
|
|
347
|
+
stack_depth[s] = max(stack_depth.get(s, 0), p.location.layer + 1)
|
|
340
348
|
conc = {idx: c for idx, zs in intra.z_by_module.items()
|
|
341
349
|
if (c := concentration_z(zs)) is not None and c >= CONCENTRATION_REVIEW_Z}
|
|
342
|
-
for idx, c in sorted(conc.items(), key=lambda kv: -kv[1])[:10]:
|
|
350
|
+
for rank, (idx, c) in enumerate(sorted(conc.items(), key=lambda kv: -kv[1])[:10]):
|
|
343
351
|
rec, pair = records[idx], pair_by_index[idx]
|
|
344
352
|
zs = intra.z_by_module[idx]
|
|
353
|
+
edge = is_edge_layer(rec.layer, stack_depth.get(pair.location.stack))
|
|
354
|
+
if rank == 0:
|
|
355
|
+
top_spike_edge = edge
|
|
356
|
+
note = ("; first/last layers often differ like this in benign adapters" if edge else "")
|
|
345
357
|
findings.append(Finding(
|
|
346
358
|
rule_id="INTRA_CONCENTRATION_SPIKE", severity=Severity.MEDIUM,
|
|
347
359
|
title="Update concentrated on a few outputs, unlike its family",
|
|
348
360
|
message=(f"{rec.module} in layer {rec.layer}: kurtosis, top-8 row share and row Hoyer "
|
|
349
|
-
f"all rise against the family (concentration z = {c:.1f} ≥ {CONCENTRATION_REVIEW_Z})"
|
|
350
|
-
|
|
361
|
+
f"all rise against the family (concentration z = {c:.1f} ≥ {CONCENTRATION_REVIEW_Z})"
|
|
362
|
+
f"{note}"),
|
|
363
|
+
evidence={"concentration_z": round(c, 3), "edge_layer": edge,
|
|
351
364
|
**{f"z_{f}": round(zs[f], 3) for f in CONCENTRATION_FEATURES}},
|
|
352
365
|
locations=[Location(layer=rec.layer, module=rec.module, expert=rec.expert,
|
|
353
366
|
tensor_key=pair.b.key)],
|
|
@@ -453,6 +466,7 @@ def _scan_inventory(path, inv: AdapterInventory, cfg_read, siblings, artifact, s
|
|
|
453
466
|
status=status, policy=policy, intra=intra.summary if intra else None, reference=None,
|
|
454
467
|
red_flags=struct.red_flags, metadata_present=bool(meta) or config is not None,
|
|
455
468
|
confidence_limits=struct.confidence_limits, n_not_analyzed=n_not,
|
|
469
|
+
top_spike_edge=top_spike_edge,
|
|
456
470
|
)
|
|
457
471
|
anomaly = Anomaly(
|
|
458
472
|
intra=intra.summary if intra else None,
|
|
@@ -40,6 +40,7 @@ def derive_verdict(
|
|
|
40
40
|
metadata_present: bool,
|
|
41
41
|
confidence_limits: list[str],
|
|
42
42
|
n_not_analyzed: int,
|
|
43
|
+
top_spike_edge: bool = False,
|
|
43
44
|
) -> Verdict:
|
|
44
45
|
"""Derive action, level, reasons, behavioural recommendation and confidence."""
|
|
45
46
|
action = "allow"
|
|
@@ -85,7 +86,9 @@ def derive_verdict(
|
|
|
85
86
|
code="INTRA_OUTLIER", severity=Severity.MEDIUM,
|
|
86
87
|
message=f"Update concentrated on a few outputs, unlike the rest of its family{where}: "
|
|
87
88
|
f"concentration z = {intra.max_concentration_z:.1f} ≥ {CONCENTRATION_REVIEW_Z} "
|
|
88
|
-
f"(threshold set at 5 % alerts on popular Hub adapters)"
|
|
89
|
+
f"(threshold set at 5 % alerts on popular Hub adapters)"
|
|
90
|
+
+ ("; it is a first/last layer, where such differences are common in benign adapters"
|
|
91
|
+
if top_spike_edge else "")))
|
|
89
92
|
raise_to("review")
|
|
90
93
|
|
|
91
94
|
for flag in red_flags:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.2"
|
|
@@ -62,6 +62,14 @@ class TestDetection:
|
|
|
62
62
|
spike = next(f for f in result.findings if f.rule_id == "INTRA_CONCENTRATION_SPIKE")
|
|
63
63
|
assert spike.locations[0].layer == 7 and spike.locations[0].module == "down_proj"
|
|
64
64
|
assert spike.evidence["concentration_z"] >= 14.0
|
|
65
|
+
assert spike.evidence["edge_layer"] is False
|
|
66
|
+
|
|
67
|
+
def test_edge_layer_alert_is_marked(self, tmp_path: Path) -> None:
|
|
68
|
+
result = scan(_write_adapter(tmp_path / "edge", inject_layer=15)) # last of 16 layers
|
|
69
|
+
spike = next(f for f in result.findings if f.rule_id == "INTRA_CONCENTRATION_SPIKE")
|
|
70
|
+
assert result.verdict.action == "review"
|
|
71
|
+
assert spike.locations[0].layer == 15 and spike.evidence["edge_layer"] is True
|
|
72
|
+
assert "benign adapters" in spike.message
|
|
65
73
|
|
|
66
74
|
def test_rescale_attack_does_not_hide_the_update(self, tmp_path: Path) -> None:
|
|
67
75
|
# B shrunk by 1e4 and lora_alpha raised by 1e4: effective ΔW is unchanged.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.0.1"
|
|
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
|
{adaptersentry-2.0.1 → adaptersentry-2.0.2}/src/adaptersentry/engine/schemas/combined_report.py
RENAMED
|
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
|