software-data-integrity-failures — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited software-data-integrity-failures (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
OWASP rank: #8 (2025) | CWEs covered: 14 | Avg incidence: 2.75%
This category focuses on failure to verify integrity of software, code, and data artifacts within your own environment — distinct from A03's upstream supply chain focus. The core concern is: can you trust what you're loading, deserializing, or executing?
Key distinction from A03 (Supply Chain):
pickle.loads() on untrusted input equals arbitrary code execution via __reduce__. This is the single most dangerous A08 vector in Python.
Dangerous functions/modules to flag:
| Module / Function | Risk | Safe Alternative |
|---|---|---|
pickle.loads() / pickle.load() | RCE via __reduce__ | JSON + Pydantic |
cPickle.loads() | Same as pickle | JSON + Pydantic |
dill.loads() | Superset of pickle, same RCE risk | JSON + Pydantic |
jsonpickle.decode() | Deserializes Python objects | json.loads() only |
shelve.open() | Backed by pickle | Redis/SQL store |
yaml.load(data, Loader=None) | Code execution via !!python/object | yaml.safe_load() |
numpy.load(f, allow_pickle=True) | RCE via pickled arrays | allow_pickle=False |
torch.load(f) | Pickle-backed by default | weights_only=True |
Detection pattern (static analysis):
# Flag any of these patterns in source code
DANGEROUS_PATTERNS = [
r'pickle\.loads?\(',
r'cPickle\.loads?\(',
r'dill\.loads?\(',
r'jsonpickle\.decode\(',
r'shelve\.open\(',
r'yaml\.load\([^)]*(?!safe_load)',
r'numpy\.load\([^)]*allow_pickle\s*=\s*True',
r'torch\.load\([^)]*(?!weights_only\s*=\s*True)',
]If pickle is unavoidable — HMAC integrity check:
import hmac, hashlib, pickle, os
SECRET = os.environ["PICKLE_HMAC_SECRET"].encode()
def safe_serialize(obj) -> bytes:
data = pickle.dumps(obj)
mac = hmac.new(SECRET, data, hashlib.sha256).digest()
return mac + data # prepend 32-byte MAC
def safe_deserialize(payload: bytes):
mac, data = payload[:32], payload[32:]
expected = hmac.new(SECRET, data, hashlib.sha256).digest()
if not hmac.compare_digest(mac, expected):
raise ValueError("Integrity check failed — data tampered")
return pickle.loads(data)Occurs when request.json or form data is passed directly into ORM .update() without field filtering — allowing attackers to set fields like is_admin=True or role="superuser".
Flask — Vulnerable pattern:
# ❌ DANGEROUS — attacker can inject any field
user = User.query.get(user_id)
user.__dict__.update(request.json)
db.session.commit()
# Also dangerous:
User.query.filter_by(id=user_id).update(request.json)Flask — Safe pattern:
# ✅ SAFE — explicit allowlist
ALLOWED_FIELDS = {"name", "email", "bio"}
data = {k: v for k, v in request.json.items() if k in ALLOWED_FIELDS}
User.query.filter_by(id=user_id).update(data)FastAPI — Pydantic prevents mass assignment by default:
# ✅ FastAPI with Pydantic — only declared fields accepted
class UserUpdate(BaseModel):
name: str | None = None
email: str | None = None
bio: str | None = None
# is_admin, role etc. cannot be set — they're not in the schema
@router.patch("/users/{user_id}")
async def update_user(user_id: int, body: UserUpdate, db: Session = Depends(get_db)):
db.query(User).filter(User.id == user_id).update(body.model_dump(exclude_unset=True))Detection pattern: In Flask, scan for request.json or request.form flowing into .update(), __dict__.update(), or ORM bulk-update calls without an intermediate allowlist filter.
If your Flask/FastAPI app renders HTML templates that load scripts or stylesheets from external CDNs, those resources must include SRI hashes to prevent CDN compromise from injecting malicious code.
Vulnerable Jinja2 template:
<!-- ❌ No integrity check — CDN compromise = XSS -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>Secure Jinja2 template:
<!-- ✅ SRI hash prevents tampered CDN delivery -->
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous">
</script>How to generate SRI hashes:
# Using openssl
curl -sL https://cdn.example.com/lib.js | openssl dgst -sha384 -binary | openssl base64 -A
# Or use srihash.org / unpkg.com automatically provides themDetection: Scan Jinja2/HTML templates for <script src="https://..."> and <link href="https://..."> tags missing integrity= attributes.
Applications that download and execute code/config updates without verifying a cryptographic signature are vulnerable to man-in-the-middle attacks.
Pattern to flag:
# ❌ Downloads and executes without verification
import urllib.request, subprocess
urllib.request.urlretrieve("https://example.com/update.sh", "/tmp/update.sh")
subprocess.run(["bash", "/tmp/update.sh"])
# ❌ Loads remote config without integrity check
import json, urllib.request
config = json.loads(urllib.request.urlopen("https://config.example.com/settings.json").read())Safe pattern — signature verification:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import urllib.request
def download_and_verify(url: str, sig_url: str, public_key_path: str) -> bytes:
data = urllib.request.urlopen(url).read()
sig = urllib.request.urlopen(sig_url).read()
with open(public_key_path, "rb") as f:
pubkey = serialization.load_pem_public_key(f.read())
pubkey.verify(sig, data, padding.PKCS1v15(), hashes.SHA256()) # raises on failure
return dataUse this checklist when auditing a FastAPI or Flask application for A08 compliance:
pickle.loads() / pickle.load() on untrusted inputcPickle, dill, jsonpickle.decode() on untrusted inputyaml.safe_load() used instead of yaml.load()numpy.load() called with allow_pickle=Falsetorch.load() called with weights_only=Trueshelve not used with untrusted keys/datarequest.json passed directly into ORM .update() without allowlistmodel.__dict__.update(untrusted_data) patternsis_admin, role, balance) not in public update schemas<script> tags have integrity= SRI attributes<link rel="stylesheet"> tags have integrity= SRI attributeseval() or exec() on remotely fetched content| Tool | What it catches | How to run |
|---|---|---|
| Bandit | pickle, yaml.load, subprocess, eval | bandit -r . -t B301,B302,B506 |
| Semgrep | Deserialization, mass assignment patterns | semgrep --config=p/python |
| pip-audit | Known-vulnerable deserialization libs | pip-audit |
| Manual grep | Quick sweep for dangerous functions | See patterns in section 1 |
Bandit rule IDs for A08:
B301 — pickle usageB302 — marshal usage (similar risk)B506 — yaml.load without safe loaderB403 — pickle import (informational)| CWE | Description | Primary Vector |
|---|---|---|
| CWE-502 | Deserialization of Untrusted Data | pickle, yaml, jsonpickle |
| CWE-915 | Improperly Controlled Modification of Dynamically-Determined Object Attributes | Mass assignment |
| CWE-829 | Inclusion of Functionality from Untrusted Control Sphere | CDN scripts without SRI |
| CWE-345 | Insufficient Verification of Data Authenticity | Missing HMAC/signature checks |
| CWE-494 | Download of Code Without Integrity Check | Auto-update without verification |
references/deserialization-exploits.md for exploit PoCs and deeper analysis~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.