Skip to content

Commit 626e349

Browse files
authored
Merge pull request #15 from BaseInfinity/v0.11.2-security-agent
v0.11.2: security agent (model + temp + sandbox)
2 parents 7cf4e7d + d8daec0 commit 626e349

6 files changed

Lines changed: 317 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,83 @@
22

33
All notable changes to opencode-sdlc-wizard.
44

5+
## [0.11.2] - 2026-05-18
6+
7+
### Added — Security agent (full set: model + temperature + tools-denial sandbox)
8+
9+
The joelhooks community config dedicates a `security` agent to "is this
10+
code safe" reviews with the same write/edit/patch denial as plan-mode.
11+
v0.11.2 ships the full surface for it: model triplet (mirrors v0.10.2
12+
planner), temperature (mirrors v0.11.1), and tool-denial sandbox
13+
(mirrors v0.10.5 plan).
14+
15+
```bash
16+
npx opencode-sdlc-wizard pick \
17+
--tier proprietary --provider anthropic \
18+
--security-tier proprietary --security-provider openai --security-model gpt-5.3-codex \
19+
--security-temp 0.1 \
20+
--sandbox-security
21+
```
22+
23+
Yields:
24+
25+
```json
26+
{
27+
"model": "anthropic/claude-opus-4-7",
28+
"provider": {
29+
"anthropic": { ... },
30+
"openai": { ... }
31+
},
32+
"agent": {
33+
"security": {
34+
"model": "openai/gpt-5.3-codex",
35+
"temperature": 0.1,
36+
"tools": { "write": false, "edit": false, "patch": false }
37+
}
38+
}
39+
}
40+
```
41+
42+
OpenCode routes security-class agent tasks to the dedicated model, with
43+
deterministic temperature and zero write capability — security reviews
44+
can never apply a patch by accident.
45+
46+
### New flags
47+
48+
- `--security-tier T --security-provider P [--security-model M]`
49+
triplet (mirrors `--reviewer-*` / `--planner-*`); writes
50+
`agent.security.model` + security provider block
51+
- `--security-temp T` — writes `agent.security.temperature` (mirrors
52+
`--coder-temp` / `--planner-temp` / `--reviewer-temp`)
53+
- `--sandbox-security` — writes `agent.security.tools = {write/edit/patch: false}`
54+
(mirrors `--sandbox-plan`)
55+
56+
All flags opt-in, all-or-nothing on the triplet, default-model fallback
57+
via the same `default_model_for()` shared with coder/reviewer/planner/small.
58+
59+
### Changed
60+
61+
- `scripts/configure-backend.sh`: 5 new flag entries, new
62+
`securityMode` block in the node heredoc, new `agent.security` cases
63+
in the sandbox + temperature emission blocks
64+
- `scripts/pick-backend.sh`: new flags, `validate_agent_triplet "security"`
65+
call, `default_model_for()` fallback, passthrough block
66+
67+
### Tests
68+
69+
- `tests/test-backend-picker.sh` adds T61–T65 (model writes + tools
70+
sandbox + triple-compose + partial-spec rejection + regression guard)
71+
- `tests/test-pick.sh` adds T39–T40 (full passthrough + regression guard)
72+
- **395 tests across 12 suites** (was 388 / 12 in v0.11.1)
73+
74+
### Compat
75+
76+
- Opt-in only. v0.10.x / v0.11.0 / v0.11.1 configs unchanged.
77+
- Composes with every prior flag.
78+
- **The full v0.11.2 hybrid in one call:** coder + small_model + planner
79+
+ reviewer + security agents, each with their own model, temperature,
80+
and (where applicable) sandbox. Five agents total covered.
81+
582
## [0.11.1] - 2026-05-18
683

784
### Added — Per-agent temperatures (`--coder-temp`, `--planner-temp`, `--reviewer-temp`)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-sdlc-wizard",
3-
"version": "0.11.1",
3+
"version": "0.11.2",
44
"description": "SDLC enforcement for OpenCode CLI — privacy-first, any-backend portability with a four-tier backend picker plus an OSS-tier cross-model-review skill so the full SDLC loop can run with zero Anthropic+OpenAI lock-in. Ships JSON Schemas for review artifacts so any consumer (cross-model-review, ditto, CI) can validate. Install with `npx opencode-sdlc-wizard init`. Sibling of agentic-sdlc-wizard and codex-sdlc-wizard.",
55
"bin": {
66
"opencode-sdlc-wizard": "cli/bin/opencode-sdlc-wizard.js"

scripts/configure-backend.sh

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ SANDBOX_PLAN=0
6666
CODER_TEMP=""
6767
PLANNER_TEMP=""
6868
REVIEWER_TEMP=""
69+
SECURITY_TEMP=""
70+
# v0.11.2 security agent. Same triplet shape as reviewer/planner;
71+
# writes agent.security.model + security provider block. Plus the
72+
# matching --sandbox-security flag for tools.{write,edit,patch}=false.
73+
SECURITY_TIER=""
74+
SECURITY_PROVIDER=""
75+
SECURITY_MODEL=""
76+
SANDBOX_SECURITY=0
6977

7078
usage() {
7179
sed -n '2,15p' "$0"
@@ -110,6 +118,15 @@ while [ $# -gt 0 ]; do
110118
--planner-temp=*) PLANNER_TEMP="${1#*=}" ;;
111119
--reviewer-temp) shift; REVIEWER_TEMP="${1:-}" ;;
112120
--reviewer-temp=*) REVIEWER_TEMP="${1#*=}" ;;
121+
--security-temp) shift; SECURITY_TEMP="${1:-}" ;;
122+
--security-temp=*) SECURITY_TEMP="${1#*=}" ;;
123+
--security-tier) shift; SECURITY_TIER="${1:-}" ;;
124+
--security-tier=*) SECURITY_TIER="${1#*=}" ;;
125+
--security-provider) shift; SECURITY_PROVIDER="${1:-}" ;;
126+
--security-provider=*) SECURITY_PROVIDER="${1#*=}" ;;
127+
--security-model) shift; SECURITY_MODEL="${1:-}" ;;
128+
--security-model=*) SECURITY_MODEL="${1#*=}" ;;
129+
--sandbox-security) SANDBOX_SECURITY=1 ;;
113130
-h|--help) usage; exit 0 ;;
114131
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
115132
esac
@@ -142,6 +159,16 @@ if [ "$PL_SET" -ne 0 ] && [ "$PL_SET" -ne 3 ]; then
142159
exit 2
143160
fi
144161

162+
# v0.11.2 security validation: same all-or-nothing rule.
163+
SC_SET=0
164+
[ -n "$SECURITY_TIER" ] && SC_SET=$((SC_SET+1))
165+
[ -n "$SECURITY_PROVIDER" ] && SC_SET=$((SC_SET+1))
166+
[ -n "$SECURITY_MODEL" ] && SC_SET=$((SC_SET+1))
167+
if [ "$SC_SET" -ne 0 ] && [ "$SC_SET" -ne 3 ]; then
168+
echo "--security-tier / --security-provider / --security-model must all be set together (or none)" >&2
169+
exit 2
170+
fi
171+
145172
# v0.10.4 small-model validation.
146173
SM_SET=0
147174
[ -n "$SMALL_TIER" ] && SM_SET=$((SM_SET+1))
@@ -163,7 +190,9 @@ node - "$TIER" "$PROVIDER" "$MODEL" "$CONFIG_PATH" "$FORCE" "$PRINT_ONLY" \
163190
"$PLANNER_TIER" "$PLANNER_PROVIDER" "$PLANNER_MODEL" \
164191
"$SMALL_TIER" "$SMALL_PROVIDER" "$SMALL_MODEL" \
165192
"$SANDBOX_PLAN" \
166-
"$CODER_TEMP" "$PLANNER_TEMP" "$REVIEWER_TEMP" <<'NODE'
193+
"$CODER_TEMP" "$PLANNER_TEMP" "$REVIEWER_TEMP" \
194+
"$SECURITY_TIER" "$SECURITY_PROVIDER" "$SECURITY_MODEL" \
195+
"$SECURITY_TEMP" "$SANDBOX_SECURITY" <<'NODE'
167196
const fs = require("node:fs");
168197
const [
169198
tier, providerArg, model, configPath, forceStr, printOnlyStr,
@@ -173,6 +202,8 @@ const [
173202
smallTier, smallProviderArg, smallModel,
174203
sandboxPlanStr,
175204
coderTempStr, plannerTempStr, reviewerTempStr,
205+
securityTier, securityProviderArg, securityModel,
206+
securityTempStr, sandboxSecurityStr,
176207
] = process.argv.slice(2);
177208
const force = forceStr === "1";
178209
const printOnly = printOnlyStr === "1";
@@ -182,12 +213,15 @@ const sandboxDocs = sandboxDocsStr === "1";
182213
const plannerMode = Boolean(plannerTier && plannerProviderArg && plannerModel);
183214
const smallMode = Boolean(smallTier && smallProviderArg && smallModel);
184215
const sandboxPlan = sandboxPlanStr === "1";
216+
const securityMode = Boolean(securityTier && securityProviderArg && securityModel);
217+
const sandboxSecurity = sandboxSecurityStr === "1";
185218
// Parse temperatures only if non-empty; empty string means "not set"
186219
// (no temperature field emitted). Numbers preserve as numbers in JSON.
187220
function parseTemp(s) { return s === "" ? null : Number(s); }
188221
const coderTemp = parseTemp(coderTempStr);
189222
const plannerTemp = parseTemp(plannerTempStr);
190223
const reviewerTemp = parseTemp(reviewerTempStr);
224+
const securityTemp = parseTemp(securityTempStr);
191225
192226
// Canonical provider IDs. Detector emits user-friendly aliases; we accept both
193227
// and emit the canonical OpenCode/models.dev ID in the written config so model
@@ -530,6 +564,18 @@ if (plannerMode) {
530564
});
531565
}
532566
567+
// v0.11.2 Security mode: identical shape to planner. Writes
568+
// agent.security.model + security provider block. Joelhooks-pattern
569+
// security agent typically pairs with --sandbox-security below.
570+
if (securityMode) {
571+
const securityProvider = PROVIDER_ALIASES[securityProviderArg] || securityProviderArg;
572+
const securityFragment = fragmentFor(securityTier, securityProvider, securityModel);
573+
merged.provider = deepMerge(merged.provider, securityFragment.provider || {});
574+
merged.agent = deepMerge(merged.agent || existing.agent || {}, {
575+
security: { model: securityFragment.model },
576+
});
577+
}
578+
533579
// v0.10.4 small_model: top-level field (not nested under agent). Distinct
534580
// from agent.plan.model — small_model is OpenCode's cross-cutting hint
535581
// for "use this when the call is cheap" (title generation, summary
@@ -549,7 +595,7 @@ if (smallMode) {
549595
// — categorical tool denial, distinct shape from the path-scoped
550596
// permission.write blocks above. Composes with v0.10.2 planner model
551597
// (agent.plan ends up with both .model AND .tools when both flags set).
552-
if (sandboxTestWriter || sandboxDocs || sandboxPlan) {
598+
if (sandboxTestWriter || sandboxDocs || sandboxPlan || sandboxSecurity) {
553599
const sandboxAdditions = {};
554600
if (sandboxTestWriter) {
555601
sandboxAdditions["test-writer"] = {
@@ -577,18 +623,27 @@ if (sandboxTestWriter || sandboxDocs || sandboxPlan) {
577623
tools: { write: false, edit: false, patch: false },
578624
};
579625
}
626+
// v0.11.2: security agent denies the same tools as plan — read +
627+
// reason, never write. Joelhooks-pattern; matches the canonical
628+
// "security review can't accidentally apply a patch" guarantee.
629+
if (sandboxSecurity) {
630+
sandboxAdditions["security"] = {
631+
tools: { write: false, edit: false, patch: false },
632+
};
633+
}
580634
merged.agent = deepMerge(merged.agent || existing.agent || {}, sandboxAdditions);
581635
}
582636
583637
// v0.11.1 per-agent temperatures. Each non-null value sets the
584638
// agent.<name>.temperature field; deep-merges with any existing model /
585639
// tools / permission siblings on that agent. --coder-temp targets the
586640
// `build` agent (OpenCode's default agent name for code generation).
587-
if (coderTemp !== null || plannerTemp !== null || reviewerTemp !== null) {
641+
if (coderTemp !== null || plannerTemp !== null || reviewerTemp !== null || securityTemp !== null) {
588642
const tempAdditions = {};
589643
if (coderTemp !== null) tempAdditions["build"] = { temperature: coderTemp };
590644
if (plannerTemp !== null) tempAdditions["plan"] = { temperature: plannerTemp };
591645
if (reviewerTemp !== null) tempAdditions["review"] = { temperature: reviewerTemp };
646+
if (securityTemp !== null) tempAdditions["security"] = { temperature: securityTemp };
592647
merged.agent = deepMerge(merged.agent || existing.agent || {}, tempAdditions);
593648
}
594649

scripts/pick-backend.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ SANDBOX_PLAN=0
7676
CODER_TEMP=""
7777
PLANNER_TEMP=""
7878
REVIEWER_TEMP=""
79+
SECURITY_TEMP=""
80+
# v0.11.2 security agent (model triplet + sandbox flag).
81+
SECURITY_TIER=""
82+
SECURITY_PROVIDER=""
83+
SECURITY_MODEL=""
84+
SANDBOX_SECURITY=0
7985

8086
while [ $# -gt 0 ]; do
8187
case "$1" in
@@ -117,6 +123,15 @@ while [ $# -gt 0 ]; do
117123
--planner-temp=*) PLANNER_TEMP="${1#*=}" ;;
118124
--reviewer-temp) shift; REVIEWER_TEMP="${1:-}" ;;
119125
--reviewer-temp=*) REVIEWER_TEMP="${1#*=}" ;;
126+
--security-temp) shift; SECURITY_TEMP="${1:-}" ;;
127+
--security-temp=*) SECURITY_TEMP="${1#*=}" ;;
128+
--security-tier) shift; SECURITY_TIER="${1:-}" ;;
129+
--security-tier=*) SECURITY_TIER="${1#*=}" ;;
130+
--security-provider) shift; SECURITY_PROVIDER="${1:-}" ;;
131+
--security-provider=*) SECURITY_PROVIDER="${1#*=}" ;;
132+
--security-model) shift; SECURITY_MODEL="${1:-}" ;;
133+
--security-model=*) SECURITY_MODEL="${1#*=}" ;;
134+
--sandbox-security) SANDBOX_SECURITY=1 ;;
120135
-h|--help) usage; exit 0 ;;
121136
*) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
122137
esac
@@ -236,6 +251,7 @@ validate_agent_triplet() {
236251
validate_agent_triplet "reviewer" "$REVIEWER_TIER" "$REVIEWER_PROVIDER" "$REVIEWER_MODEL"
237252
validate_agent_triplet "planner" "$PLANNER_TIER" "$PLANNER_PROVIDER" "$PLANNER_MODEL"
238253
validate_agent_triplet "small" "$SMALL_TIER" "$SMALL_PROVIDER" "$SMALL_MODEL"
254+
validate_agent_triplet "security" "$SECURITY_TIER" "$SECURITY_PROVIDER" "$SECURITY_MODEL"
239255

240256
# Resolve reviewer-model default if --reviewer-tier + --reviewer-provider
241257
# set but --reviewer-model not. Same default-model map as the coder pin.
@@ -265,6 +281,15 @@ if [ -n "$SMALL_TIER" ] && [ -n "$SMALL_PROVIDER" ] && [ -z "$SMALL_MODEL" ]; th
265281
}
266282
fi
267283

284+
# Security agent default-model fallback.
285+
if [ -n "$SECURITY_TIER" ] && [ -n "$SECURITY_PROVIDER" ] && [ -z "$SECURITY_MODEL" ]; then
286+
SECURITY_MODEL="$(default_model_for "$SECURITY_TIER" "$SECURITY_PROVIDER")" || {
287+
echo "pick: no default model known for security $SECURITY_TIER/$SECURITY_PROVIDER." >&2
288+
echo " Pass --security-model <name> explicitly." >&2
289+
exit 4
290+
}
291+
fi
292+
268293
# Forward to configure-backend.sh. --dry-run becomes --print-only on the
269294
# configurator (it prints the merged JSON but does not write the file).
270295
CONFIGURE_ARGS=(--tier "$TIER" --provider "$PROVIDER" --model "$MODEL")
@@ -298,6 +323,15 @@ fi
298323
[ -n "$CODER_TEMP" ] && CONFIGURE_ARGS+=(--coder-temp "$CODER_TEMP")
299324
[ -n "$PLANNER_TEMP" ] && CONFIGURE_ARGS+=(--planner-temp "$PLANNER_TEMP")
300325
[ -n "$REVIEWER_TEMP" ] && CONFIGURE_ARGS+=(--reviewer-temp "$REVIEWER_TEMP")
326+
[ -n "$SECURITY_TEMP" ] && CONFIGURE_ARGS+=(--security-temp "$SECURITY_TEMP")
327+
if [ -n "$SECURITY_TIER" ]; then
328+
CONFIGURE_ARGS+=(
329+
--security-tier "$SECURITY_TIER"
330+
--security-provider "$SECURITY_PROVIDER"
331+
--security-model "$SECURITY_MODEL"
332+
)
333+
fi
334+
[ "$SANDBOX_SECURITY" = "1" ] && CONFIGURE_ARGS+=(--sandbox-security)
301335

302336
if [ -n "$REVIEWER_TIER" ]; then
303337
echo "pick: resolved coder $TIER/$PROVIDER$MODEL + reviewer $REVIEWER_TIER/$REVIEWER_PROVIDER$REVIEWER_MODEL" >&2

0 commit comments

Comments
 (0)