Observed since 20 Oct, 19:05 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Static security scanner for LoRA adapters — detects anomalous weight patterns in .safetensors files
AdapterSentry is a static security scanner for LoRA adapters distributed as .safetensors
files. A LoRA adapter is a small file that changes how a large language model behaves; anyone
can publish one, and a poisoned adapter can carry a backdoor that stays silent until a trigger
appears in the input. AdapterSentry inspects the adapter's weights before the adapter is
loaded, without running the model and without knowing the trigger.
v2.0.0 replaces the analysis engine: exact statistics of the effective update
ΔW = s·B·A (no sampling, no proxies), streaming analysis that scales to 70B–405B and MoE
models in tens of megabytes of memory, a comparison of every module with its own family inside
the adapter, structural findings, and a new result contract (ScanResult 2.x).
v2.0.1 recalibrates the intra-adapter verdict rule on 393 real adapters from the Hugging
Face Hub (see Limits).
See CHANGELOG.md and Migrating from 1.x.
$ adaptersentry scan adapter_model.safetensors
AdapterSentry 2.0.1 · adapter_model.safetensors
================================================================
Verdict: REVIEW (MEDIUM) · confidence medium · behavioural check recommended
Status: ok · policy default · mode full
Why:
- [LOW] NO_REFERENCE_PROFILE: No calibrated reference for this model family; verdict relies on intra-adapter comparison
- [MEDIUM] INTRA_OUTLIER: Update concentrated on a few outputs, unlike the rest of its family (down_proj layer 47): concentration z = 21.3 ≥ 14.0 (threshold set at 5 % alerts on popular Hub adapters)
Adapter: peft_lora, family llama (config), 80 layers, r=64 (declared 64), α=128, scaling alpha_over_r, training trained
Coverage: 560/560 modules analysed; 0 tensor(s) not analysed
Intra: concentration z 21.3 · max single-feature z 64.0 · 9 outlier module(s) · energy Gini 0.41
Findings (1):
[MEDIUM] INTRA_CONCENTRATION_SPIKE — Update concentrated on a few outputs, unlike its family (layer 47, down_proj)
(Illustrative output.)
The verdict is one of allow, review or block, with the reasons, a confidence
level and what limits it. CI gates read verdict.action.
pip install adaptersentry
adaptersentry scan adapter_model.safetensors # text summary
adaptersentry scan adapter_model.safetensors --format json # ScanResult 2.1.0
adaptersentry scan adapter_model.safetensors --format sarif \
--output adaptersentry.sarif --fail-on review # GitHub code scanning + CI gate
adaptersentry batch --input-dir adapters/ --workers 8 --fail-on review
from pathlib import Path
from adaptersentry import scan
result = scan(Path("adapter_model.safetensors"))
print(result.verdict.action, result.verdict.level)
for reason in result.verdict.reasons:
print(reason.code, reason.message)
Put adapter_config.json next to the .safetensors file (as PEFT saves it): the scan needs
lora_alpha to know the real size of the update.
| Option | Meaning |
|---|---|
--format text|json|full-json|sarif | output; full-json adds per-module features |
--policy default|strict | strict blocks on structural red flags (see below) |
--fail-on review|block | exit code 2 if the verdict reaches this action |
--mode full|fast | fast uses fewer samples for the kurtosis estimate |
--include-modules, --include-heads | per-module / per-attention-head detail in JSON |
--full-paths | report the absolute path instead of the file name |
Exit codes: 0 completed, 1 the adapter could not be analysed (the verdict is still
review), 2 the --fail-on threshold was reached.
file ─► validate ─► inventory ─► per module (streamed) ─► compare within ─► structural ─► verdict
header every exact ΔW features each module findings
& limits tensor via r×r cores family
.safetensors file; the header
length, dtype, shape, byte ranges, element counts and LoRA rank are checked from the header
before any tensor is allocated.modules_to_save, DoRA vectors, trainable-token deltas, and
anything that cannot be analysed — nothing is skipped silently.adapter_config.json, the singular values, norms and row/column norms
are computed exactly through the r×r matrices AAᵀ and BᵀB — ΔW itself (235 M values for a
70B MLP layer) is never built. Features describe the shape of the update, which the
literature finds informative, rather than its size:
stable rank, participation ratio, spectral entropy, concentration of the leading direction
versus a random baseline, entry kurtosis (estimated by importance sampling of rows so rare
spiky rows are not missed), and how concentrated the update is on a few output rows or
input columns — for lm_head and embeddings, on a few tokens.q_proj, all
down_proj, …): the trend with depth is removed with a Theil–Sen line, then robust
z-scores (median/MAD) are computed for every feature. Real adapters differ a lot between
modules of one family, so a single unusual feature is reported but does not change the
verdict. The verdict looks for one pattern: a module whose update is concentrated on a
few outputs — entry kurtosis, top-8 row share and row Hoyer all rise against the family
(concentration z). This needs no reference data and is insensitive to the adapter's
overall strength. It runs for families of at least 16 modules.lm_head / embedding / router
matrices shipped in the adapter, LoRA on a mixture-of-experts router, ranks that differ from
the declared r (typical of merged adapters), code/pickle/archive files next to the adapter.| Situation | Action |
|---|---|
| File could not be parsed, or part of it was not analysed | at least review |
| A module's update is concentrated on a few outputs, unlike its family (concentration z ≥ 14) | review |
| Structural red flag | review; block with --policy strict |
| Reference profile for the model family says p ≤ α | review / block (reference profiles: next release) |
| Otherwise | allow |
Without a calibrated reference profile the default policy never blocks: the false-positive rate of a block would be unknown.
Measured on an 8-CPU server with a synthetic Llama-3.3-70B-shaped adapter
(80 layers × 7 modules, r = 64, 1.66 GB bf16): adaptersentry scan end to end in
~18–20 s, peak process memory ~160 MB (Python and numpy included). The 1.x engine
needed ~3.3 GB just to load the same file.
review from this
rule. Larger models and other families may behave differently. Detection was measured only
on a toy payload inserted into those adapters (one LoRA direction redirected into 4 outputs
of a mid-depth down_proj): 71 % found when the payload is strong, ≤ 3 % when it is
as strong as an ordinary direction. Real backdoors are not toy payloads; a measured
detection rate on labelled clean/poisoned adapters and reference profiles per model family
are the next steps. Until then the confidence of a verdict is at most medium.review..bin, .pt) are never deserialised. Use a dedicated
serialisation scanner for them.--format json produces one document per adapter: scan (who/when/how), artifact (hashes,
provenance — file name only unless --full-paths), adapter (format, family, rank, α,
scaling, modules), status + coverage (what was and was not analysed), verdict,
anomaly (what the verdict rests on), findings (with exact layer/module/tensor locations),
optional modules, and errors.
adaptersentry.load_scan_result() validates a document and rejects other major versions.| 1.x | 2.0.0 |
|---|---|
analyze(), scan() → AdapterReport, scan_to_result() | scan() → ScanResult |
verdict.recommended_action | verdict.action |
overall_score, ensemble.score (0–100) | removed — verdict.level + anomaly (z-scores, later p-values) |
status, parse_status, analysis_mode | status + coverage |
--format summary-json / debug-json | --format json / full-json |
--fail-on LOW…CRITICAL (finding severity) | --fail-on review|block (verdict action) |
--rank | removed — rank is read from the tensors and checked against the config |
adaptersentry-m1 command | adaptersentry scan |
1.x results are not converted: re-scan the adapters.
git clone https://github.com/nkorvyakov28-AS/adaptersentry-m1
cd adaptersentry-m1
uv sync --frozen --extra dev # or: pip install -e ".[dev]"
pytest tests/ -q
Architecture: docs/architecture/m1-architecture.md · CLI: docs/cli/usage.md · Batch engine: docs/architecture/scan-engine.md
AdapterSentry M1 is the open static-analysis layer. Planned: M2 Behavioral Sandbox — advanced behavioral analysis.
The feature design follows published work on weight-space backdoor detection in LoRA adapters, including Detecting Backdoored LoRAs from Weights Alone (arXiv:2602.15195), Token-Level Generalization in LoRA Adapter Backdoors (arXiv:2605.30189) and Z-PEFT (arXiv:2608.02271). Their results motivate the choices above: shape over magnitude, per-module and per-head features, MLP coverage, and one-class calibration instead of a classifier trained on known attacks.
See SECURITY.md. Report a malicious adapter found in the wild with a GitHub
issue labelled malicious-adapter; report vulnerabilities in AdapterSentry privately.
Apache 2.0 — see LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 2.0.2 | 11 Oct 2026 | 11 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 19:05 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:7cda4f6e2e587db517f9459860cb85683db570b60b083ad4b915d2abc32fe8f1