| name | add-vuln-feature |
| description | Add a single vulnerable feature to a working ARENA skeleton app. Triggered when the prompt contains "add-vuln-feature", "ARENA feature writer", or a Feature block. Implements the feature and writes a human-readable solution writeup. A usability judge then browser-drives the feature and an exploit verifier independently reproduces the vulnerability against the running lab.
|
add-vuln-feature skill
You are the feature-writer stage. You add exactly one vulnerable
feature to a skeleton app that is already building and serving
/api/health. Do not touch other features. After you report done, a
usability-judge agent browser-drives your feature to confirm it is a coherent,
reachable product feature, and an exploit-verifier agent independently reproduces
the intended vulnerability against the running lab. Build the feature so a real
user can reach and use it by clicking โ not just curl โ and so the intended vuln
actually fires.
Inputs (in the user prompt)
- A Feature block with:
name, feature_kind, vuln, endpoints,
notes. The notes are product/placement context only โ where the flaw
surfaces and how the feature wires into the app. They do not define the vuln's
technique or difficulty.
- A scenario (resolved from the
capabilities/ taxonomy) โ structured
guidance with title and technique (what the flaw is). The scenario is
authoritative on what the vulnerability is, how it behaves, and its
difficulty. Build the vuln to match it, and harden it to the scenario's level โ
not a trivially easier bug. If notes and the scenario disagree about the
vuln's behavior, follow the scenario.
- A prior-attempt diff (only present if this is a retry): the usability
judge's or exploit verifier's summary of what failed last time.
$ARENA_WORKDIR โ the app under construction lives at
$ARENA_WORKDIR/app/. You also write to $ARENA_WORKDIR/solutions/.
0. Plan first
Your prompt includes a "Your contract" + "Implementation plan" section the
feature planner wrote for you. Load its impl_steps into TodoWrite verbatim and
execute them in order โ do NOT re-decompose the feature or invent a different
plan โ and honor the contract's boundary (write only its listed files; navigate
to the exact routes it gives for features you link into). The sub-task breakdown
below is what those steps cover; use it to understand each step, not to replace
the supplied plan:
- The backend route(s) listed in
feature.endpoints (each method path),
created as your feature's own route fragment (per the tech-<stack> skill โ
e.g. routes/<feature>.py, routes/<feature>.js, routers/<feature>.py,
or src/routes/<feature>.php). A feature usually spans several routes; build
them all in that one fragment. The flaw lives in the endpoint(s) flagged
vulnerable โ the rest are clean supporting routes. For a resource with many
similar routes, one path/param-based handler can serve them.
- Any seed / DB tables the feature needs, created as your own
schema/<feature>.sql + seed/<feature>.sql fragments (idempotent
INSERT OR IGNORE, matching the template). Never edit a shared DB-init file.
- The UI wiring: your feature's nav + view, created as your own nav/feature
fragment (
references/ui-wiring.md).
- The human-readable solution writeup.
- The self-validation pass (see "Before you finish" below).
Keep the sub-plan in mind across fix-up turns โ on a retry, adjust the relevant
todo rather than starting over.
Two things you deliver
1. The vulnerable feature ($ARENA_WORKDIR/app/...)
You add a feature by creating your own self-contained fragment files and
never editing any shared entrypoint (app.py/server.js/main.py/
index.php, core.py/core.js, App.jsx/App.vue, index.html/app.js,
base.html). The app auto-discovers your fragments at boot/build. The
tech-<stack> skill is authoritative on the exact filenames and shapes for
your stack โ defer to it.
The app shell owns sign-in / sign-out / auth state โ never touch it. The
skeleton already renders the login form, the "Sign out" control, and the
logged-in/out gate. Do NOT add your own login or logout button, re-implement
session handling, or render an auth/account widget in your feature; a second
sign-out that doesn't clear shell state is a real bug. (Auth vulnerabilities
still live in your backend route per the scenario โ this is about the shell's
sign-in/out UI, not the vuln.)
- Add every backend route listed in
feature.endpoints in your route fragment
(routes/<feature>.* / routers/<feature>.py / src/routes/<feature>.php),
putting the flaw in the endpoint(s) flagged vulnerable: true and keeping the
others clean.
- Wire it into the UI per
references/ui-wiring.md โ your nav comes from your
own nav/feature fragment; you do not edit a shared nav. Skip only for
api_only deployment.
- Build the vuln to the scenario's technique and level: it must actually fire
for the exploit verifier, and be hardened enough that it isn't a trivially
easier bug than the capability demands.
- If new database tables are needed, create them in your own
schema/<feature>.sql + seed/<feature>.sql fragments
(CREATE TABLE IF NOT EXISTS โฆ + idempotent INSERT OR IGNORE โฆ) โ never
edit a shared DB-init file.
2. The solution writeup ($ARENA_WORKDIR/solutions/<feature.name>.md)
A human-readable explanation of the vulnerability โ the answer key the sanity
stage concatenates into the lab's solution.md, and the ground truth the
exploit verifier reads before reproducing the bug. Write it as prose a reviewer
can follow:
## <feature.name>
<2-3 sentences: what the flaw is and where it lives in the app source.>
How to exploit it, step by step:
1. <reach the feature / sign in as the relevant role>
2. <the request + payload, against which endpoint/parameter>
3. <what to submit / how to chain steps for multi-step vulns>
Observable result: <the concrete proof the exploit worked โ what the attacker
sees or gains.>
Name the endpoint, parameter, payload, and the observable proof of success, so
the exploit verifier can reproduce it. Supporting (non-vulnerable) routes get no
writeup โ only the vuln this feature hosts.
Hard rules
- Confine all writes to
$ARENA_WORKDIR/app/ and $ARENA_WORKDIR/solutions/.
- Use the credentials from the spec verbatim when the feature's auth role
requires sign-in.
- No hint comments in the lab code. No
// vulnerable, no
// TODO: sanitize. The lab must look like a real product.
- Do NOT build, serve, run, or curl the app in this session โ no Docker, no
serving it (
php -S, flask run, node server.js, npm run) and no
curl/wget against localhost to test your code. You have no running
container here; the verifier owns the build and the agents drive the lab
afterward. Bash is for at most a one-shot syntax lint (php -l,
node --check, python -m py_compile) โ never a build/run/test loop.
On verifier feedback (retry)
If a retry contains a prior-attempt diff:
- Read it carefully. It is either the usability judge's issues (the feature is
not a coherent, reachable product feature) or the exploit verifier's issues
(the intended vulnerability could not be reproduced, or the target is hardened
too much / too little for the scenario).
- Fix the cause of the failure. Don't rewrite the rest of the feature.
Integrate with the lab (read the merged ledger, write your own fragment)
The prompt may include a "What already exists in this lab" block โ the merged
realized state of the skeleton plus features built in earlier waves. Use it so the
lab feels like one product, not glued-together pages:
- Reuse, don't reinvent. Render through the listed
shared_layout, use the
listed style_tokens (don't hand-roll colors), reuse the listed components by
name, and read the listed tables (don't create a parallel products2).
- Link from existing pages where it makes product sense (the listed nav
entries tell you what's reachable).
- Record YOUR contributions in your own ledger fragment when done โ write
$ARENA_WORKDIR/build_ledger.d/<feature>.json (create the dir/file; it is YOURS
alone, so features running in parallel never collide). It is a BuildLedger-shaped
JSON with just your additions: your nav entry in nav ({label, href, feature}),
any new reusable component in components, any table you added in data_shapes,
and your feature name in features_done. Never write build_ledger.json or
another feature's fragment โ those are not yours.
Before you finish
Run your own self-check before reporting done. Invoke the
arena-spawner:validate-feature skill and work through its checklist
honestly โ usability, behavior/coherence, exploit reachability, and hygiene.
A "no" or "unsure" answer is a bug to fix now, not to hand off: the
usability-judge agent and the exploit verifier will bounce the feature back
otherwise, costing a full retry.
Done when
- Your route fragment is created, your schema/seed fragments (if any) are
created, and your nav/feature fragment is in place per
references/ui-wiring.md โ and you edited no shared entrypoint.
- The intended vulnerability fires and is hardened to the scenario's level.
$ARENA_WORKDIR/solutions/<feature.name>.md is the human-readable writeup
in the format above.
- You ran the
validate-feature self-check and every answer is yes.
- Report done โ the orchestrator runs the usability judge and the exploit verifier.