From soundcheck
Detects mass assignment vulnerabilities when creating or updating database records from unfiltered request bodies, form data, or JSON payloads into ORM calls. Suggests input DTOs and allowlists.
npx claudepluginhub thejefflarson/soundcheck --plugin soundcheckThis skill uses the workspace's default tool permissions.
Protects against mass assignment (also called auto-binding or object injection) where
Detects mass assignment vulnerabilities in PHP/Laravel code. Flags unguarded Request::all() in create/update, missing $fillable/$guarded, unfiltered array spreads, and Doctrine entity setters.
Tests API endpoints for mass assignment vulnerabilities by injecting unauthorized fields like role, isAdmin, price, balance into requests. Useful for OWASP API3:2023 BOLA audits in Rails, Django, Express, Spring apps.
Tests APIs for mass assignment vulnerabilities by injecting undocumented fields (role, isAdmin, price, balance) into writable endpoint requests to detect unfiltered binding. For OWASP API security audits.
Share bugs, ideas, or general feedback.
Protects against mass assignment (also called auto-binding or object injection) where
an attacker adds unexpected fields like role=admin or is_verified=true to a request
body and the ORM blindly persists them. Exploitation leads to privilege escalation,
account takeover, and data corruption.
User.objects.create(**request.json) — Django: all request fields written to the modelBeanUtils.copyProperties(dto, entity) — Spring: copies every matching field with no filterdb.Create(&user) after json.Decode(body, &user) — GORM: decoded JSON sets all struct fields including protected onesdiesel::insert_into(users).values(&new_user) where new_user is deserialized from the full request body without selecting fieldsObject.assign(dbRecord, req.body) — Express/Node: merges all body keys into the recordFlag the vulnerable pattern and explain the risk. Then suggest a fix that establishes these properties:
role,
permissions, is_admin, is_verified, balance, owner_id, tenant_id
— these come from the authenticated session or database defaults, never from
the payload, even after "validation".BeanUtils.copyProperties(dto, entity) with no
ignore list is the exact bug — the safe form names the fields.ALLOWED = {"name", "email"} set filtered once per endpoint is brittle; the
typed input pattern makes omission a compile-time (or deserialization-time)
guarantee.Anchor — shape, not implementation:
struct CreateUserInput { name: String, email: String } # no role, no is_admin
input = deserialize(request.body, as=CreateUserInput) # unknown fields dropped
user = User { name: input.name, email: input.email, role: DEFAULT_ROLE }
db.insert(user)
Confirm the following properties hold (language-agnostic):