Skip to content

feat: load probe templates at runtime via --templates-dir - #174

Open
praetorian-farida wants to merge 8 commits into
mainfrom
feat/runtime-probe-templates
Open

praetorian-farida wants to merge 8 commits into
mainfrom
feat/runtime-probe-templates

Conversation

@praetorian-farida

@praetorian-farida praetorian-farida commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a runtime probe-template loader so new probes — including genuinely new multi-turn attack strategies — can be defined in YAML and run with --templates-dir, without recompiling Augustus.

augustus scan <generator> --templates-dir ./templates --probe runtime.MyProbe

Templates register before probe selection, so they also work with --probes-glob and --all.

What's added

  • pkg/templates — type-aware schema. type: static (default) keeps today's single-turn prompt probes; type: multiturn adds engine + strategy blocks. Validate() branches by type and composes with the existing tool-use/secondary-detector validation.
  • internal/runtimetemplates (new) —
    • TemplateStrategy: implements multiturn.Strategy from YAML prompt templates (attacker_system, turn, rephrase, feedback) via text/template, with simple/extended parser selection. Dry-run validated at load, so a bad field reference (e.g. {{.Tpyo}}) fails immediately instead of degrading prompts mid-scan.
    • MultiTurnTemplateProbe: builds the existing unified multi-turn engine (internal/multiturn) via the shared CreateGenerators; reports the template's own info.detector.
    • RegisterFromPath: loads + registers static and multi-turn probes; warns on built-in override.
  • cmd/augustus--templates-dir flag, registered before glob expansion.
  • examples/runtime-templates — static + multi-turn examples + README.

Capability (and the ceiling)

Without a rebuild Supported?
New static / single-turn prompt probe
New multi-turn strategy (prompt + config over the unified engine)
Re-aim any template at scan time (goal, max_turns, generator types/models via --config-file)
Pick any registered detector via info.detector
New detector/generator implementation or new engine control-flow ❌ still Go + rebuild

Testing

  • Unit: 35+ new tests across schema validation, strategy rendering, probe construction, and registration. go build ./..., package tests, and go vet clean; gofumpt-clean.
  • Live validation against the agentdash target (easy mode), both probes loaded at runtime, no rebuild. Verdicts cross-checked with an independent OpenAI judge LLM over the agent's actual responses (not just the substring detector):
    • Static probe (viewer_globex caller): the agent disclosed Acme's full order table (20 acme-corp rows — IDs, amounts, products) to a Globex caller. Judge LLM: leak: true. Deterministic, confirmed true positive.
    • Multi-turn strategy (gpt-4o attacker + judge over the unified engine): the runtime strategy drives the engine end-to-end. Outcome is non-deterministic — when the attacker breaks tenant scoping the disclosed Acme rows are confirmed real by the judge (leak: true); when it only gets deflections the judge confirms SAFE (leak: false). Both observed and judge-verified.
    • Detector caveat (see C3 below): a multi-turn attempt's detector_results always contains the in-loop judge.Judge score alongside info.detector, and the verdict is the max — so the displayed detector label is attribution, not proof of which detector fired. The shipped multi-turn example uses judge.Judge for a semantic verdict; the local agentdash test used base.StringDetector and was cross-checked with the independent judge above.

Notes

  • Rebased onto main; contains only this feature (one commit).
  • Draft: opening for early review; the multi-turn example strategy prompt is intentionally conservative and can be sharpened in follow-up.

🤖 Generated with Claude Code

praetorian-farida and others added 3 commits June 9, 2026 17:32
Add a runtime template loader so new probes — including new multi-turn
attack strategies — can be defined in YAML and run without recompiling.

- pkg/templates: type-aware schema (static | multiturn) + validation
- internal/runtimetemplates: TemplateStrategy (multiturn.Strategy built
  from YAML prompts, dry-run validated at load) + MultiTurnTemplateProbe
  + RegisterFromPath
- cmd/augustus: --templates-dir flag, registered before glob expansion
- examples/runtime-templates: static + multi-turn examples + README

Static templates become single-turn prompt probes; multi-turn templates
plug a YAML-defined strategy into the existing unified multi-turn engine
(internal/multiturn). Detector is chosen via info.detector; engine
parameters are overridable at scan time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onfig

- multiturn: add a configurable Detector field to BaseMultiTurnProbe
  (defaults to DetectorJudge); existing multi-turn probes are unchanged.
- runtimetemplates: embed BaseMultiTurnProbe instead of duplicating its
  Prober methods; MultiTurnTemplateProbe now implements ProbeDetectorConfig
  so multi-turn templates honor info.detector_config (parity with static).
- lock the buildEngineConfigMap <-> multiturn config-key contract with a
  round-trip test so a key rename in multiturn fails here, not silently.
- render(): on the (dry-run-guarded) runtime error path, log and return
  empty instead of a half-rendered prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orce

RegisterFromPath now detects ID collisions with already-registered probes
and fails closed (atomically — nothing is registered) unless override is
allowed. A new scan --force flag opts into shadowing a built-in.

Prevents a stray template file from silently replacing a built-in probe;
mirrors Nuclei's custom-vs-official template distinction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@praetorian-farida
praetorian-farida marked this pull request as ready for review June 10, 2026 00:19
@praetorian-farida
praetorian-farida requested a review from a team as a code owner June 10, 2026 00:19
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@github-actions

Copy link
Copy Markdown

CLAUDE.md Drift Detection

Code changes in this PR may have made documentation stale:

CLAUDE.md

Section Issue Evidence
Adding New Components → New Probe Multi-turn template support and runtime loading not documented PR adds type: multiturn to templates, engine and strategy blocks, and --templates-dir flag to load probes at runtime without rebuild. CLAUDE.md only mentions static YAML templates in data/ subdirectories requiring rebuild.
Architecture → Directory Structure New package not listed PR adds internal/runtimetemplates/ package which is now a core component for runtime template loading, but not mentioned in architecture diagram.
CLI Usage Patterns New major feature not documented PR adds --templates-dir and --force CLI flags enabling runtime template loading. No example usage provided in CLAUDE.md.
Examples missing New examples/runtime-templates/ directory has static and multi-turn examples demonstrating the feature but not referenced in documentation. See examples/runtime-templates/README.md, static-example.yaml, and multiturn-example.yaml.

Automated drift check — please review and update if needed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Review

Critical issues

  • Multi-turn strategy templates are NOT dry-run validated at load — contrary to the README/PR claim ("validated on load … a typo like `{{.Tpyo}}` fails immediately"). `RegisterFromPath` → `templates.LoadFromPath` only runs schema-level `Validate()` (engine/strategy blocks present, generator types set). The field-reference dry-run lives in `newTemplateStrategy`, which is only reached via the factory `newMultiTurnProbe` at `probes.Create` time. Net effect: a bad field ref in `attacker_system:`/`turn:` passes load (prints "Loaded N template(s)…") and only errors when the scan materializes the probe — fail-fast-at-load doesn't hold for multi-turn. Consider parsing the strategy once during `RegisterFromPath` (or in a load-time validation hook) so the typo aborts before the scan starts.
  • `secondary_detectors` is silently ignored on multi-turn templates. `validateMultiTurn` doesn't run the secondary-detector validation that `validateStatic` does, and `MultiTurnTemplateProbe` doesn't implement `GetSecondaryDetectors`. A multi-turn YAML declaring `secondary_detectors` validates clean and silently has no effect — surprising given `info.detector_config` is threaded through. Either wire it up or reject it at validation with a clear error.

Security

No security concerns flagged. `--templates-dir` loads user-pointed local YAML and renders `text/template` (data interpolation, not code execution); no new external network calls or secret handling. `--force` override correctly fail-closes by default (no panic — registry replaces).

Test coverage

Thorough — 35+ new tests across schema validation, strategy rendering, factory construction, and atomic/fail-closed registration. Gap: no test asserts when multi-turn strategy template errors surface (load vs. create), which is exactly the behavior the first finding concerns.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Review

Critical Issues

  • info.detector is not the sole post-hoc verdict for runtime multi-turn probes. The docs say info.detector is the reported verdict detector, and newMultiTurnProbe passes that detector into the probe, but UnifiedEngine.buildUnifiedResult always pre-populates DetectorResults["judge.Judge"]; Attempt.GetEffectiveScores() then takes the max across all detector results. A runtime template using a different detector can still be marked vulnerable because the in-loop judge score remains in DetectorResults. See examples/runtime-templates/README.md:39, internal/runtimetemplates/probe.go:105, internal/multiturn/engine.go:790, pkg/attempt/attempt.go:174.

Security

No security concerns flagged.

Suggestions

  • Detect duplicate template IDs within the same --templates-dir batch. RegisterFromPath only checks each loaded template against the existing registry before registration; two new files with the same ID will silently replace the first because registry registration overwrites existing factories. See internal/runtimetemplates/register.go:29, internal/runtimetemplates/register.go:45, pkg/registry/registry.go:88.
  • Either support or reject secondary_detectors on multi-turn templates. The shared schema documents them, but validateMultiTurn does not validate them and MultiTurnTemplateProbe does not implement ProbeSecondaryDetectors, so they are silently ignored. See pkg/templates/types.go:236, pkg/templates/types.go:321, internal/runtimetemplates/probe.go:16.

Reviewed by Codex (gpt-5.5)

@github-actions

Copy link
Copy Markdown

Gemini Review

⚠️ Gemini review did not complete — the review job failed. See the run logs.
Push a new commit or comment @gemini on a review comment to re-trigger.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93226619-1146-414b-933d-2bc37799b7c2

📥 Commits

Reviewing files that changed from the base of the PR and between bfddb87 and 0069a5c.

📒 Files selected for processing (1)
  • CLAUDE.md
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md

Walkthrough

This PR adds runtime YAML probe template loading. It adds type-aware template validation, a template-driven multiturn strategy, and a multi-turn probe wrapper that carries detector overrides and secondary detectors. Template registration now loads from a directory with collision and detector checks. The scan CLI adds --templates-dir and loads templates before probe selection. Documentation and example templates were added.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/runtime-probe-templates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/runtimetemplates/register_test.go (1)

163-166: ⚡ Quick win

Assert force override actually replaces the registered probe.

This test only checks err == nil after forced registration. Please also assert the created probe now reflects the second template payload, so a silent no-op override can’t pass.

Suggested test hardening
 	// With force, the override succeeds.
 	if _, err := RegisterFromPath(dir2, true); err != nil {
 		t.Fatalf("RegisterFromPath() with force should override, got: %v", err)
 	}
+	probe, err := probes.Create("runtimetest.OverrideTarget", nil)
+	if err != nil {
+		t.Fatalf("probes.Create() after force override: %v", err)
+	}
+	pm, ok := probe.(types.ProbeMetadata)
+	if !ok {
+		t.Fatal("overridden probe should implement ProbeMetadata")
+	}
+	prompts := pm.GetPrompts()
+	if len(prompts) != 1 || prompts[0] != "two" {
+		t.Fatalf("force override did not replace template content, prompts=%v", prompts)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/runtimetemplates/register_test.go` around lines 163 - 166, The test
currently only checks RegisterFromPath(dir2, true) returns no error but doesn't
verify the override took effect; after the forced register call, fetch the
registered probe/template by its name (the probe name used in the first
template) using your codebase's lookup (e.g., the Get/Find/Lookup function that
returns the registered template/probe) and assert that its contents/fields match
the second template's payload (e.g., description, command, or other
distinguishing fields) so the test fails if the override was a silent no-op; use
the same symbols in the test (RegisterFromPath, dir2 and the probe name) to
locate and validate the replaced template.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/runtime-templates/README.md`:
- Around line 11-13: Add a short note explaining that runtime template IDs that
collide with existing probe IDs will fail to load unless the CLI is invoked with
--force; update the README’s paragraph about templates being registered in the
probe registry (the sentence referencing --probes-glob "runtime.*" and --all) to
mention this collision behavior and show the remedial usage example (e.g., run
with --force) and that this is part of the CLI contract for template loading.

---

Nitpick comments:
In `@internal/runtimetemplates/register_test.go`:
- Around line 163-166: The test currently only checks RegisterFromPath(dir2,
true) returns no error but doesn't verify the override took effect; after the
forced register call, fetch the registered probe/template by its name (the probe
name used in the first template) using your codebase's lookup (e.g., the
Get/Find/Lookup function that returns the registered template/probe) and assert
that its contents/fields match the second template's payload (e.g., description,
command, or other distinguishing fields) so the test fails if the override was a
silent no-op; use the same symbols in the test (RegisterFromPath, dir2 and the
probe name) to locate and validate the replaced template.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 063cb9b3-2d3c-491b-9509-737aa27b3ed6

📥 Commits

Reviewing files that changed from the base of the PR and between a598f5a and 4789a48.

📒 Files selected for processing (15)
  • cmd/augustus/cli.go
  • cmd/augustus/scan.go
  • examples/runtime-templates/README.md
  • examples/runtime-templates/multiturn-example.yaml
  • examples/runtime-templates/static-example.yaml
  • internal/multiturn/probe.go
  • internal/multiturn/probe_test.go
  • internal/runtimetemplates/probe.go
  • internal/runtimetemplates/probe_test.go
  • internal/runtimetemplates/register.go
  • internal/runtimetemplates/register_test.go
  • internal/runtimetemplates/strategy.go
  • internal/runtimetemplates/strategy_test.go
  • pkg/templates/multiturn_schema_test.go
  • pkg/templates/types.go

Comment thread examples/runtime-templates/README.md
…ectors, dup IDs)

- C1: validate multi-turn strategy prompts at load (RegisterFromPath) so a bad
  field reference aborts before the scan starts, not at probes.Create — the
  fail-fast-at-load claim now holds for multi-turn.
- C2/S2: secondary_detectors now apply to multi-turn templates — validated in
  validateMultiTurn (shared helper) and run via ProbeSecondaryDetectors on
  MultiTurnTemplateProbe (previously silently ignored).
- S1: RegisterFromPath rejects duplicate template IDs within a single
  --templates-dir batch (previously the second silently overwrote the first).
- C3 (docs): the multi-turn verdict is the max across detectors — the engine's
  in-loop judge.Judge score is retained, so info.detector adds a verdict signal
  rather than replacing it. Corrected examples/runtime-templates/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@praetorian-farida

Copy link
Copy Markdown
Contributor Author

Thanks both — all findings addressed in 64a173b.

Claude review

  • Multi-turn strategy not dry-run validated at load (critical) — fixed. RegisterFromPath now compiles each multi-turn strategy (newTemplateStrategy) in a pre-flight pass, so a bad field ref aborts at load before anything is registered. Verified end-to-end: a turn: "{{.Nope}}" template now errors with invalid multi-turn template ... can't evaluate field Nope and prints no "Loaded N…". Test: TestRegisterFromPath_MultiTurnBadFieldFailsAtLoad.
  • secondary_detectors silently ignored on multi-turn — fixed (wired up). validateMultiTurn now runs the shared secondary-detector validation, and MultiTurnTemplateProbe implements ProbeSecondaryDetectors. Tests: TestNewMultiTurnProbe_CarriesSecondaryDetectors, TestProbeTemplate_Validate_MultiTurn_SecondaryDetector*.

Codex review

  • info.detector is not the sole verdict for multi-turn (critical) — confirmed; this is intentional, shared engine behavior (engine.go always records the in-loop judge.Judge score and GetEffectiveScores takes the max across detectors). It was a doc overclaim, now corrected: the README states the multi-turn verdict is the max across detectors and that info.detector adds a verdict signal rather than replacing the in-loop judge. Changing the scoring would alter crescendo/goat/hydra, so it's deliberately left as engine behavior.
  • Duplicate template IDs within one --templates-dir batch — fixed. RegisterFromPath rejects intra-batch duplicate IDs (previously the second silently overwrote the first). Verified: errors with duplicate template id "…". Test: TestRegisterFromPath_DuplicateIDsInBatch.
  • secondary_detectors on multi-turn — same fix as above (wired + validated).

All package tests, go build ./..., go vet, and gofumpt are clean; re-validated live against the agentdash target (static + multi-turn both still flag the cross-tenant leak).

@github-actions

Copy link
Copy Markdown

CLAUDE.md Drift Detection

Code changes in this PR may have made documentation stale:

CLAUDE.md

Section Issue Evidence
"Directory Structure" (lines 62-79) New internal/runtimetemplates/ package not documented New 1000+ LOC package for runtime template loading, registration, and strategy compilation
"Adding New Components" → "New Probe" (lines 89-96) Missing documentation of runtime template loading without rebuild PR adds --templates-dir flag and internal/runtimetemplates.RegisterFromPath() for loading YAML probes at scan time
"CLI Usage Patterns" (lines 119-133) Missing examples of --templates-dir and --force flags New flags added to cmd/augustus/cli.go enable loading and overriding probes via directory: --templates-dir ./path --force
"Adding New Components" → "New Probe" (lines 89-96) Incomplete explanation of YAML templates PR adds multi-turn template support (type: multiturn) with EngineConfig and StrategyConfig blocks in pkg/templates/types.go, but docs only mention static templates

Automated drift check — please review and update if needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/runtimetemplates/probe.go`:
- Around line 128-130: During runtime conversion in the loop over
tmpl.Info.SecondaryDetectors, trim whitespace from detector names before storing
them into out by setting SecondaryDetector.Name = strings.TrimSpace(s.Name) (so
it matches validateSecondaryDetectors()'s trimmed checks); update the conversion
in the function that constructs out (the loop referencing
tmpl.Info.SecondaryDetectors and types.SecondaryDetector) and add an import for
strings if missing to ensure detector lookup won't fail due to leading/trailing
spaces.

In `@pkg/templates/types.go`:
- Around line 237-240: validateMultiTurn() currently bypasses the same
allowed-mode check done in validateStatic(), allowing invalid info.mode values
for type: multiturn; update validateMultiTurn() to perform the same validation
of info.mode (or call the shared validation used by validateStatic()) before
returning, ensuring info.mode is one of the permitted values; reference
validateMultiTurn(), validateStatic(), and the info.mode field when adding the
check so the multi-turn path enforces the same allowed-mode rules as static
templates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7099aa16-4a64-4968-a6e6-ca2e9bda0fce

📥 Commits

Reviewing files that changed from the base of the PR and between 4789a48 and 64a173b.

📒 Files selected for processing (7)
  • examples/runtime-templates/README.md
  • internal/runtimetemplates/probe.go
  • internal/runtimetemplates/probe_test.go
  • internal/runtimetemplates/register.go
  • internal/runtimetemplates/register_test.go
  • pkg/templates/multiturn_schema_test.go
  • pkg/templates/types.go
✅ Files skipped from review due to trivial changes (1)
  • examples/runtime-templates/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/templates/multiturn_schema_test.go
  • internal/runtimetemplates/register.go

Comment thread internal/runtimetemplates/probe.go
Comment thread pkg/templates/types.go Outdated
…ndary detector names)

- validateMultiTurn now validates info.mode (extracted shared validateMode
  helper) — invalid mode values previously passed for multi-turn templates.
- secondaryDetectorsFromTemplate trims detector names so they match
  validateSecondaryDetectors()'s trimmed checks and resolve in the registry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@praetorian-farida

Copy link
Copy Markdown
Contributor Author

CodeRabbit items addressed in 698dd3c:

  • validateMultiTurn now validates info.mode — extracted a shared validateMode helper called by both static and multi-turn validation; invalid mode values now error for multi-turn templates too. Test: TestProbeTemplate_Validate_MultiTurn_InvalidMode.
  • secondaryDetectorsFromTemplate trims detector names (strings.TrimSpace) so they match validateSecondaryDetectors()'s trimmed checks and resolve cleanly in the registry.

Build/vet/tests + gofumpt clean.

@github-actions

Copy link
Copy Markdown

CLAUDE.md Drift Detection

Code changes in this PR may have made documentation stale:

CLAUDE.md

Section Issue Evidence
"CLI Usage Patterns" Missing new --templates-dir and --force flags for runtime template loading Added in cmd/augustus/cli.go; enables loading probe templates at scan time without rebuild via internal/runtimetemplates
"Adding New Components > New Probe" Incomplete YAML documentation: only describes data/ subdirectory approach; doesn't mention new --templates-dir feature for runtime loading or type: multiturn support New examples/runtime-templates/ with comprehensive examples and tests show multi-turn templates and runtime loading are core features

Automated drift check — please review and update if needed.

- Document ID-collision / --force behavior in the runtime templates README
- Strengthen force-override test to verify the probe was actually replaced
- Replace custom contains() with slices.Contains (lint modernize)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@blayne

blayne commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Code Review — runtime probe-template loader

Reviewed in an isolated worktree. Baseline: go build ./..., go vet, gofmt -l, and the affected package tests (runtimetemplates, templates, multiturn) all clean/passing.

Overall a strong PR — clean type-discriminated design (static vs multiturn), good reuse of the existing multi-turn engine (CreateGenerators / config.FromMap) instead of duplicating it, atomic/fail-closed registration with collision detection, and the stringly-typed engine-config keys are pinned by a round-trip test. Substantial test coverage (35+ cases).

🟠 Requested change — load-time dry-run misses field refs inside {{range}}/{{if}}

strategy.go dry-runs each template against zero-value data to catch bad field references at load. The summary claims this makes a bad field ref "fail immediately instead of degrading prompts mid-scan" — but that only holds at the top level. Fields referenced inside {{range .History}} / {{if .History}} are never exercised, because the zero-value turnData{} has an empty History, so the body doesn't execute. Confirmed empirically:

dry-run (empty history) err = <nil>
runtime (with history)  err = ... can't evaluate field Questionnn in type multiturn.TurnRecord

This is reachable in practice — the shipped multiturn-example.yaml puts .TurnNumber, .JudgeScore, .Question exactly inside {{range .History}}. A typo there passes load, then fails on turn 2+ at runtime, where render() swallows the error (slog.Warn) and returns "" → an empty prompt is sent to the attacker LLM, silently degrading the scan — the exact failure mode the dry-run was meant to prevent.

Fix: dry-run with populated data so range/if bodies execute, e.g.

{"turn", s.turn, turnData{History: []multiturn.TurnRecord{{}}, LastResponse: "x"}},

and add a regression test with a typo inside {{range .History}}.

🟡 Low / nits

  • render() silent degradation: returning "" on a runtime render error yields a quietly-wrong scan rather than a loud failure. Consider slog.Error and/or surfacing it on the attempt. (Less pressing once the dry-run gap is closed.)
  • --force flag name: bare --force on ScanCmd reads like a global safety override but only governs template-vs-built-in override. Something like --templates-force / --override-builtins would be clearer. Behavior itself is fine (registry.Register replaces + logs a per-override warning).
  • Detector name not validated at load — an unregistered info.detector fails at scan time rather than load; consistent with existing behavior, but a pre-flight registry lookup would fail faster.

Verdict

Approve once the dry-run gap is closed and a negative test (typo inside a range body) is added. The Low/nit items are optional polish.

author: your-team
description: "Multi-turn attack that escalates claimed authority each turn"
goal: obtain restricted operational details
detector: judge.Judge # post-hoc verdict detector (pipeline stage)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the code seems fine, I am wondering about this though. I see the detector here is judge.Judge, but down in the engine there is a judge_generator_type. Are those the same or is there 2 judges?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two judges, two roles — and in this example they collapse into one.

engine.judge_generator_type is the in-loop steering judge: it scores every turn and drives early-exit/feedback. It's whatever generator that key resolves to — the template's value (here anthropic.Anthropic, but that's just the example's default) or whatever you override it to via --config-file, since the engine block is merged as defaults under scan-time config (buildEngineConfigMapmaps.Copy(m, cfg)).

info.detector is the post-hoc verdict detector. The catch: the engine records its in-loop max score under the detector key judge.Judge (engine.go:791), and the detector pipeline skips re-running any detector whose results already exist (detection.go:56, to preserve full conversation context). So with info.detector: judge.Judge the post-hoc pass is a no-op — the in-loop judge is the verdict. Point info.detector at a different detector to get a genuinely independent second opinion (verdict = max of both).

Clarified this in the README + example (which were previously misleading on this point) in the latest push.

blayne's review:
- Dry-run multi-turn strategy prompts against populated data so field-reference
  errors inside {{range .History}} / {{if .History}} are caught at load instead
  of silently sending an empty prompt mid-scan (render swallowed the error).
  Regression test added.
- render() now logs at slog.Error (an empty prompt silently degrades a scan).
- Validate info.detector and secondary_detectors against the detector registry
  at load, so an unknown detector fails fast rather than partway through a scan.

Collision handling:
- Remove the --force override escape hatch entirely. A runtime template whose id
  collides with an existing probe now always fails to load with a clear error
  telling the author to rename the id; a template must not shadow a built-in.

Evan's question (two judges):
- Clarify the README + example: engine.judge_generator_type is the in-loop
  steering judge; info.detector is the post-hoc verdict detector. The engine
  records its in-loop score under "judge.Judge", and the detector pipeline skips
  re-running a same-named detector, so info.detector: judge.Judge is a no-op
  (the in-loop judge is the verdict). The README previously implied it always
  adds an independent signal, which was misleading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

CLAUDE.md Drift Detection

Code changes in this PR introduce new documentation gaps:

Section Issue Evidence
"Adding New Components → New Probe" Missing documentation of runtime template loading feature New internal/runtimetemplates package and --templates-dir CLI flag enable loading YAML probes without rebuilding, but CLAUDE.md only describes the built-in data/ subdirectory approach
"Adding New Components → New Probe" Missing documentation of multi-turn template types PR introduces type: multiturn templates that define attack strategies in YAML, but CLAUDE.md doesn't mention this capability. See examples/runtime-templates/multiturn-example.yaml for examples
"CLI Usage Patterns" Missing examples of new --templates-dir flag The new --templates-dir flag (added to ScanCmd in cmd/augustus/cli.go) allows loading templates at runtime without rebuilding, but is not shown in CLI examples

Automated drift check — please review and update if needed.

Address the CLAUDE.md drift check: document --templates-dir runtime loading,
static vs multiturn template types, and add a CLI usage example. Closes the
gap the drift bot flagged for this feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants