Skip to content

Security & reliability hardening: dashboard auth, SSRF pinning, tool policy, corpus integrity, signed findings log - #21

Open
pt-act wants to merge 20 commits into
JailbrokenAI:mainfrom
pt-act:upstream-contrib/security-remediation
Open

Security & reliability hardening: dashboard auth, SSRF pinning, tool policy, corpus integrity, signed findings log#21
pt-act wants to merge 20 commits into
JailbrokenAI:mainfrom
pt-act:upstream-contrib/security-remediation

Conversation

@pt-act

@pt-act pt-act commented Jul 23, 2026

Copy link
Copy Markdown

Security & Reliability Hardening — Dashboard Auth, SSRF Pinning, Tool Policy, Corpus Integrity, Signed Findings Log

This PR contributes the full hardening work done on the pt-act/wallbreaker fork back to
upstream. It combines two landed efforts into one coherent security series:

  1. Audit remediation — 50 findings from a full application audit (wallbreaker-audit.md:
    3 Critical, 13 High, 16 Medium, 14 Low, 4 Informational), previously merged to the fork as
    PRs Remove dead/duplicate/unused code + fix undefined model_id #1/Fix: log no longer force-scrolls to bottom on every message #2/Load .env at CLI startup #3.
  2. Roadmap-implementation hardening — post-audit fixes that removed the fragility left in
    the shipped security code, restored a clean gated test baseline, finished deferred residuals,
    and added a signed findings log + opt-in judge ensemble.

Capability/ASR work is intentionally excluded and will come as a separate PR (see
Scope below), so this series stays a focused, reviewable security change.

Why this PR exists

The dashboard shipped as an unauthenticated local FastAPI server whose routes could spawn
shell commands, write API keys to .env, and fire attacks — reachable via browser CSRF from any
page the operator visited, and across the LAN if bound to 0.0.0.0. That was browser-driven RCE

  • credential exfiltration + SSRF-to-cloud-metadata on a "localhost dev tool." The original audit
    rated it "Do not ship." This series closes that entire class and hardens the surrounding
    reliability and supply-chain posture.

Finding table — Critical & High (representative)

ID Severity Fix
SEC-1/2/3 Critical Per-launch bearer token (0600) + pure-ASGI SecurityMiddleware + Origin/Sec-Fetch-Site same-origin check on every /api/* route
SEC-4 Critical SSRF egress guard (scheme allowlist, block loopback/link-local/metadata/RFC1918) + DNS-rebind socket-IP pinning (PinnedEgressBackend)
SEC-5 High read_file realpath confinement + symlink rejection
SEC-6/8 High Attack-firing + config/metadata routes behind auth+CSRF
SEC-7 High Bind guard: refuses non-loopback --host without --allow-remote
SEC-9/10/11 High Run-log redaction + 0600/0700 perms + path guard; Pydantic request models; global 500 handler
REL-1/2/6/7 High Vision-judge NameError fix; provider lifecycle close at tool-call boundary; run force-stop + wall-clock timeout
RACE-1..4 High Atomic state writes (tmp+fsync+os.replace+lock); cache delta format; gate/RunLog locking

"Do not ship → Safe to ship"

  • Before: unauthenticated browser-CSRF RCE, credential exfiltration, SSRF to metadata, a
    confirmed vision-judge crash, HTTP client leak, non-atomic state with lost-update races.
  • After: authenticated + same-origin-gated API; least-privilege tool policy (host tools
    opt-in only for the browser agent); SSRF guard with DNS-rebind pinning that fails closed if
    the underlying transport shape changes; atomic state; WCAG 2.2 AA dashboard; supply-chain
    corpus pinning; tamper-evident signed findings log; and a required CI gate.

What's new (roadmap-implementation layer on top of the audit fixes)

  • Egress de-fragilizationmake_pinned_transport() self-checks that the pinned backend is
    installed and raises rather than returning an un-pinned transport if httpx internals change;
    httpx pinned to a verified range and matrix-tested. Two-tier policy documented: advisory
    check_url (fail-open on NXDOMAIN) can never widen the enforcing PinnedEgressBackend
    (fail-closed).
  • Supply-chain corpus integritylibrary.lock.toml pins each runtime-fetched corpus to a
    commit SHA; loader fails closed on mismatch/unresolved; wallbreaker corpus verify CLI.
  • Reusable hardening toolkitagent_dashboard_harden/ re-exports the security layer
    (SecurityMiddleware, egress guard, tool policy) with zero behavior change, plus
    parameterizable PBT fixtures for the 5 security-property categories.
  • Signed findings logwallbreaker/findings_log.py: append-only Ed25519-signed JSONL;
    tamper-evident; the private key is never included in the exported bundle.
  • Opt-in judge ensemblejudging.run_ensemble: up to 3 judges concurrently, majority-vote
    label + mean±1σ, low-agreement verdicts flagged UNCERTAIN; single-judge default unchanged.
  • Test baseline & CI gate — pre-existing corpus-dependent failures quarantined
    (xfail/skipif); .github/workflows/redteam-gate.yml runs the PBT suite + an httpx version
    matrix + -W error::ResourceWarning as required checks.
  • Frontend — the three oversized dashboard components decomposed below the 400-line guideline
    with a check:line-counts guard; new vitest + jest-axe coverage.

New / notable files

Path Purpose
wallbreaker/dashboard/auth.py Pure-ASGI token + Origin/CSRF gate (SEC-1/2/3)
wallbreaker/tools/egress_guard.py SSRF guard + PinnedEgressBackend + make_pinned_transport (SEC-4, fail-closed)
wallbreaker/tools/tool_policy.py Least-privilege registry for the browser agent
agent_dashboard_harden/ Reusable, zero-behavior-change security toolkit + PBT fixtures
wallbreaker/findings_log.py Ed25519 signed findings log
library.lock.toml + wallbreaker/tools/parsel_engine.py Corpus SHA pinning + verifier
tests/pbt/test_security_properties.py, tests/test_tg{3,5,7}_*.py Security/correctness properties

Verification

  • Backend (warm): 1191 passed / 39 skipped / 31 xfailed, pytest -q exits 0.
  • Backend (cold checkout, corpora absent): 1175 passed / 55 skipped / 31 xfailed, exit 0 —
    corpus-dependent tests skip, nothing fails.
  • Frontend: 60 vitest tests / 12 files, jest-axe clean, tsc clean.
  • PBT: 8 security/correctness properties execute in the committed runner (access control,
    egress fail-closed + DNS-rebind, corpus SHA gate, token 0600, signed-log tamper-evidence,
    ensemble concurrency).
  • Independently PM-validated across 3 rounds (verdict: approved).

Scope

This PR is security & reliability only. The fork's separate capability track
(engine-capability-uplift: semantic strategy retrieval, target-family bandit routing, agentic
attack-surface completion, cross-family transfer) is deliberately not included and will be
proposed as its own PR so this series can be reviewed and merged on its security merits alone.

Responsible use

Wallbreaker is for authorized LLM red-teaming and safety evaluation only. This PR changes only
the harness's own security posture; it does not alter the tool's red-teaming capabilities or its
responsible-use doctrine.

Poncho (audit-remediation M0) and others added 20 commits July 19, 2026 10:29
… path confinement, log redaction, atomic state, image-judge + provider hardening

Closes (backend, headlessly-tested): SEC-1/2/3/6 (auth token + CSRF/Origin middleware),
SEC-1/4/5 (dashboard tool-exposure policy), SEC-4 (egress guard on http_request + discovery),
SEC-5/10 (read_file confinement + symlink-safe run-log path), SEC-7 (bind guard),
SEC-9 (log redaction + 0600/0700 perms), REL-1 (vision_complete NameError),
REL-3/RACE-1 (atomic state + merge), REL-11 (anthropic KeyError), REL-12 (claude_code killpg).
Adds tests/test_audit_remediation.py (33 focused + Hypothesis tests). Full suite: only
pre-existing corpus (ENI/L1B3RT4S) failures remain.
SEC-1/2 closure on the SPA side (Checkpoint A #5):
- api.ts: lazy memoized ensureToken() fetches /api/session once; withAuth()
  injects X-WB-Token into the central j() helper (all api.* calls) AND the
  streaming runAgent fetch (SSE via fetch+ReadableStream, not EventSource,
  so custom headers work). No token -> no header (test-factory mode works).
- auth.py: drop CSRF_HEADER. The token in a custom header IS the CSRF defense
  (cross-site page can't set a custom header without a CORS preflight, which
  loopback-only CORS rejects; can't read /api/session either). The explicit
  Origin/Sec-Fetch-Site same-origin check is the independent CSRF guard. No
  cookie auth -> a double-submit CSRF token would add nothing. Removing the
  unenforced header eliminates the false-sense-of-protection (PM directive:
  never send a header you don't check).
- server.py /api/session: drop csrfHeader from the response; doc the model.
- tests: lock the SPA contract (/api/session shape, no csrfHeader, cross-site
  Origin -> 403; no-auth mode -> empty token).
- .gitignore: guard gh_token.txt / *_token.txt / *.pat / dashboard token.

Verified: npm run build clean; 35/35 test_audit_remediation.py; full suite
1097 passed (was 1095), 31 failed / 7 errors unchanged (pre-existing corpus-
dependent: ENI/L1B3RT4S/system_prompts/persona_forge/seed_sweep).
The leak: ~80 build_provider() sites in tools build a pooled httpx.AsyncClient
per provider and never close it; across an autonomous run this accumulates
hundreds of unclosed clients (ResourceWarning, FD pin, defeats pooling).
The dashboard brain provider (built outside tools) leaked per run too.

Fix (chokepoint, not 80 per-site try/finally):
- providers/factory.py: a ContextVar bucket + provider_scope() async ctx
  manager. build_provider() appends to the active bucket; ToolRegistry.execute
  wraps every tool call in `async with provider_scope()`, aclose()ing all
  providers built during the call at the call boundary. A provider reused
  across the call (best_of_n's single target, used for all N fires) stays
  pooled for the whole call, closed once at the end — pooling preserved,
  not killed. Child tasks (gather_capped/create_task) share the bucket by
  reference so they're tracked too. Fake-tolerant: monkeypatched build_provider
  replaces this function entirely (fakes hold no real client); the close loop
  uses getattr(aclose) so any tracked provider missing aclose is skipped.
- providers/base.py: __aenter__/__aexit__ on Provider (the explicit-ownership
  primitive; used by the dashboard brain path, available for future sites).
- tools/registry.py: execute() wrapped in provider_scope().
- dashboard/server.py: _LiveAttackerProvider.aclose closes the brain provider;
  switch() closes the predecessor on hot-swap; runner() finally aclose()s the
  brain provider at run end. No double-close: CLI/TUI top-level providers are
  built outside reg.execute (bucket None, untracked) and close themselves.

Verification (PM gate):
- tests/test_audit_remediation.py +5: chokepoint closes a real client;
  pooling preserved mid-call; monkeypatched no-aclose fake doesn't raise;
  async-with closes; brain aclose + switch closes old. 40/40 pass.
- pytest -q -W error::ResourceWarning: 1102 passed, 31 failed / 7 errors
  unchanged (pre-existing corpus-dependent: ENI/L1B3RT4S/system_prompts/
  persona_forge/seed_sweep). No new ResourceWarning failures, no fake broken.
- Full suite: 1102 passed (was 1097 -> +5), no regressions.

Design note for review: deviated from the letter of 'async with build_provider
at ~80 sites' in favor of one chokepoint (provider_scope at reg.execute) —
serves the same intent (close at tool-invocation boundary, preserve pooling,
fake-tolerant, fewer places to get wrong) with materially lower rework risk
and zero fake edits. __aenter__/__aexit__ still added so the explicit pattern
is available where clean.
…L-6/7)

The dashboard's agent_run discarded the runner task ref (GC risk, no cancel
handle), cleared agent_active only in finally so a hung run wedged the
dashboard forever (409 on every new run), had no overall wall-clock timeout
(a trickling target hung indefinitely), and an unbounded SSE queue.

Fix (preserving the correct detach-drains behavior the audit praised):
- Retain the runner task: agent_task strong ref; cleared in runner() finally
  and /api/agent/stop. No more GC-mid-flight risk.
- POST /api/agent/stop: idempotent force-stop. Returns {stopped: false} (200)
  when nothing is running (never errors); {stopped: true} when it cancels a
  live task, awaits its finally (5s grace) so a new run can start immediately.
- Overall wall-clock deadline: asyncio.wait_for around run_autonomous.
  Generous + configurable: body run_timeout_s, else max_rounds*180s floored
  300s / capped 7200s — never kills a legitimate long run, recovers a wedged
  one. On TimeoutError emits a terminal 'timeout' SSE event BEFORE closing.
- Bounded SSE queue (maxsize=1024) with drop-oldest on overflow — NEVER blocks
  the producer: a slow/disconnected client must not stall the run. The original
  stream_attached no-op (run completes server-side regardless of client) is
  preserved; REL-6 bounds memory, it does NOT kill runs on disconnect.
- Every exit path (normal, exception, wait_for timeout, force-stop
  CancelledError) lands in the runner's finally, clearing agent_active /
  agent_task / agent_control. CancelledError is caught + emitted as a terminal
  'stopped' event (not re-raised) so the stop endpoint's await returns cleanly.
- AGENTS.md: recorded the provider-lifecycle invariant (the one debt AD-12
  introduces) — tools must not cache build_provider() across execute() calls.

Verification:
- tests/test_audit_remediation.py +3: a trickling target trips the timeout
  (terminal 'timeout' event); /api/agent/stop cancels a wedged run then a new
  run starts (idempotent stop returns {stopped:false} when idle); a client
  disconnect lets the run finish server-side (regression guard for the
  detach-drains behavior). 43/43 pass.
- tests/test_dashboard.py: 14/14 pass (existing agent_run + settings unchanged
  — run_timeout_s is a body field, not persisted into the settings view).
- Full suite: 1105 passed (was 1102 -> +3), 31 failed / 7 errors unchanged
  (pre-existing corpus-dependent).
…ock (RACE-2/3/4)

Batch of the TG5 concurrency-integrity fixes (specs/audit-remediation tasks.md).

TG5.3 — RACE-2 ResultCache (wallbreaker/cache.py):
- Versioned the cache format. v1 (legacy) = cumulative snapshots per line; v2 = single-
  sample deltas (one line per put). The loader is tolerant of BOTH: it keeps the last v1
  snapshot per key and adds v2 deltas on top, so an existing v1 file migrates correctly
  and new v2 deltas append without double-count (the #1 rework risk). Verified by a
  backward-compat test that hand-writes a legacy v1 file then appends v2 deltas.
- Multi-process append is now safe without a lock: a sub-PIPE_BUF line appended in 'a'
  mode is atomic on POSIX, so interleaved writers no longer last-writer-wins undercount.
- Compaction: once the file exceeds _COMPACTION_THRESHOLD lines, rewrite it as one
  cumulative line per key via the shared atomic_write helper (tmp+fsync+os.replace).
  Deltas are the source of truth; a crash mid-compaction loses nothing (temp abandoned).
  Bounded on-disk growth.

TG5.5 — RACE-3 request_gate (wallbreaker/providers/request_gate.py):
- notify_request_gates(): iterates every live gate on the running loop and notifies it
  WHILE HOLDING the Condition lock (notify_all outside the lock is a no-op/raises), so a
  raised concurrency limit promptly frees tasks parked at the OLD limit. No-op outside a
  loop or with no gates. Wired into the async agent_run site after configure_request_gate.
  (Per-run gate scoping deferred — the dashboard already enforces one agent run at a time
  via agent_active + dashboard_inference_lock, so the process-global gate's last-writer-
  -wins-across-concurrent-ops is largely moot in practice; the notify fix is what closes
  RACE-3 as reported.)

TG5.4 — RACE-4 RunLog (wallbreaker/session.py):
- RunLog._write now holds a threading.Lock across seq++ + append, and the 'no await
  inside _write' invariant is documented in-line. _write is synchronous today (line
  atomicity relies on that); the lock is a guard rail so a future refactor that adds an
  await can't silently interleave half-lines across coroutines. Closed (not deferred).

Shared helper (wallbreaker/_fsutil.py):
- Extracted atomic_write (temp+fsync+os.replace) to one place; state.py and cache.py
  both use it (no duplicated crash-safe write impl).

Optional TG4.3 refinement folded in: an explicit run_timeout_s is honored as-is (no
7200s clamp) — only the DERIVED default is capped, so a deliberate multi-hour battery
isn't silently truncated.

Verification:
- tests/test_audit_remediation.py +6: v2 deltas sum on load (incl. a cross-process
  append); v1 backward-compat (no double-count); compaction rewrites one line/key
  preserving totals; notify wakes parked tasks on a limit raise (without a release);
  notify is a no-op outside a loop; RunLog concurrent writes stay line-atomic + seq-
  monotonic + unique. 49/49 pass.
- touched-module tests (cache/request_gate/state/session/dashboard/logging/
  session_load_runlog): 58 passed.
- Full suite: 1111 passed (was 1105 -> +6), 31 failed / 7 errors unchanged (pre-existing
  corpus-dependent). No regressions.
…nue (SEC-11/REL-8)

The last P1 backend group. Closes SEC-11 (no 500 tracebacks; body validation)
and REL-8 (failures surfaced, not silently swallowed).

TG3.5 — Pydantic request models (SEC-11):
- AgentRunRequest, FireComposeRequest, ProviderUpsert, SettingsUpdate with
  extra='ignore'/'allow' + all-optional defaults so valid SPA traffic and the
  existing test payloads are never 422'd for unknown fields or missing
  optionals. Handlers convert via model_dump(exclude_defaults=True) so 'in'
  checks behave identically to the old body: dict (only client-sent fields
  appear in the dump). Wrong-typed fields (max_rounds as a list, etc.) → 422
  at the boundary, never a 500 traceback.
- Settings and provider models use extra='allow' (too free-form for strict
  fields; the handler's _int_setting + isinstance do the real validation).

TG3.4 — Global 500 handler (SEC-11):
- @app.exception_handler(Exception) returns a generic {detail: 'internal
  server error'} with NO traceback/paths. Scoped to Exception only: FastAPI's
  built-in HTTPException (4xx) and RequestValidationError (422) handlers take
  precedence by type specificity — never turns a 401/403/404/400/422 into 500.
  SSE StreamingResponse errors are unaffected (response already started;
  handled by runner's try/except).
- Fixed a real leak the test caught: the runner's error_event sent
  f'{type(exc).__name__}: {exc}' through the SSE stream — which included
  internal paths. Now logs full error server-side (_log.exception) and pushes
  a generic 'internal error' to the client.

TG3.6 — Startup except narrowing (REL-8):
- The create_app init 'except Exception: pass' → 'except Exception as exc:
  _log.warning(...)' — log-and-continue, never hard-raise. A dashboard that
  starts and reports degraded state (provider_registry=None → routes 400)
  is better than one that won't start at all. The failure is now visible.

Verification:
- tests/test_audit_remediation.py +6: malformed body (wrong types) → 422 not
  500; missing required field → 422; unknown fields don't 422 valid traffic;
  forced internal error → generic 500 with no path/traceback leak; HTTPException
  (401/403/404) not intercepted by the 500 handler; startup degrades
  log-and-continue on a broken config. 55/55 pass.
- tests/test_dashboard.py: 14/14 pass (existing agent_run + settings payloads
  unchanged — run_timeout_s is body-only, settings 4-keys contract preserved).
- Full suite: 1117 passed (was 1111 -> +6), 31 failed / 7 errors unchanged
  (pre-existing corpus-dependent). No regressions.
… (M1-backend)

Gate 3 — Property-Based Testing (Hypothesis). Every backend security-critical
component now exists, so the runnable properties from
specs/audit-remediation/pbt-properties.py are wired into tests/pbt/ against
the actual post-remediation APIs.

tests/pbt/test_security_properties.py (NEW, 14 properties + 1 skipped):
- Prop 1 Access control: unauth/cross-site → 401/403, no side effect (200 cases)
- Prop 10 Token file 0600
- Prop 2 Least-privilege: default registry SAFE-only; host ⇔ opt-in
- Prop 3 SSRF confinement: private/link-local/meta always denied (300 cases)
- Prop 4 Path confinement: _confine redirects or flags escapes (300 cases)
- Prop 5 Input validation: clamp-or-4xx, never 5xx (200 cases)
- Prop 6 Data integrity: state round-trip + concurrent-merge + cache conservation
- Prop 7 Secret non-exposure: no secret survives redact_args (400 cases)
- Prop 8 Concurrency: gate never exceeds limit under any schedule (50 cases)
- _int_setting clamps within bounds (300 cases)
- SSRF stable under redirect (metadata IP in chain → blocked)
- Run-log symlink guard rejects symlinked targets

PBT FOUND a real SEC-11 bug: _int_setting(float('inf')) raised uncaught
OverflowError → 500. Fixed: the except now catches (TypeError, ValueError,
OverflowError). The PBT property test_int_setting_clamps_within_bounds pins it.

security-audit-prep.md: Tier-2 checkboxes → [x]; documented the RACE-2
compaction append/replace race as a conscious residual (undercount-only,
cache is not the system of record, tiny window, low priority follow-up) per
PM directive. DNS-rebinding residual also documented.

This is the M1-backend checkpoint: all P1 backend task groups landed
(TG1.4, TG4.2, TG4.3, TG5.3/5.4/5.5, TG3.4-3.6), Gate 3 PBT green.

Verification:
- tests/pbt/test_security_properties.py: 14 passed, 1 skipped (stego extra).
- tests/test_audit_remediation.py: 55/55 pass.
- tests/test_dashboard.py: 14/14 pass.
- Full suite: 1131 passed (+14 PBT), 31 failed / 7 errors unchanged (corpus).
Audit remediation M0+M1: dashboard auth, tool policy, egress, reliability (draft)
…A + visual consistency

TG6 (reliability): shared primitives (useAbortableFetch, AsyncView, Dialog, Combobox,
InteractiveChip, LiveRegion); REL-4 (SSE AbortController+unmount cleanup), REL-5 (stale-guards),
REL-9 (AsyncView error states), REL-10 (Profiles busy guards), REL-14 (Pointer Events resize),
INFO-1 (no-dangerouslySetInnerHTML test); Vitest+RTL+jest-axe harness.
TG7 (a11y): A11Y-1..13 (Dialog semantics+focus trap/restore, Combobox aria, keyboard chips/rows,
LiveRegion, non-color verdict cues, contrast, labels/autocomplete/fieldset, landmarks/h1/skip link,
reduced-motion, touch targets); axe 0-violations per view.
TG8 (visual): VIS-1..5 (chip standardization, token/class inline-style cleanup, async states,
shared src/format.ts formatters, layout-shift min-heights + pinned auto-scroll).
Verify: tsc clean, 38/38 vitest, vite build ok.
Frontend audit remediation: reliability primitives + WCAG 2.2 AA + visual consistency (TG6-8)
…PBT, REL-13 retry cap

P3.1: DNS-rebind socket-IP-pinning — PinnedEgressBackend wraps httpcore's
network backend to resolve, validate, and pin the TCP connection to the
validated IP, closing the TOCTOU gap between check_url() and connect.
http_tool.py now uses make_pinned_transport() for all outbound requests.

P3.2: create_app(require_auth=True) default flip (AD-6) — the factory is
now secure by default. Updated 29 create_app calls across 5 legacy test
files to pass require_auth=False explicitly.

P3.3: Gate 4B integration PBT — 4 new Hypothesis properties proving
cross-site requests cannot reach host tools or private egress targets,
even with allow_host_tools=True. Also tests PinnedEgressBackend rejects
direct private-IP connects.

P3.4: REL-13 retry cap — gated_stream/gated_request now accept
max_attempts parameter (default 6, non-idempotent cap 2). Existing
is_concurrency_limit filter already narrow (only 'concurrent'/'rate limit
exceeded' 429s, not 'insufficient_quota'). No-retry-after-yielded-tokens
behavior verified.

Tests: 1146 passed (+25 P3), 31 failed / 7 errors unchanged (pre-existing
corpus). PBT: 18 properties + 1 skipped.
…LOG with full remediation history

- wallbreaker-audit.md: added Gate 4 closure header at top (all 50 findings
  closed, release rec flipped to Safe to ship) and updated §9 release
  recommendation to reflect the remediated state. Original audit text preserved.
- CHANGELOG.md: comprehensive entry covering all implementations across PRs
  #1 (M0+M1 backend: SEC-1..11, REL-1..12, RACE-1..4, tool policy, PBT),
  #2 (P2 frontend: TG6 reliability primitives, TG7 WCAG 2.2 AA, TG8 visual),
  and #3 (P3 hardening: DNS-rebind pinning, require_auth default, Gate 4B
  PBT, REL-13 retry cap). Deferred items documented.
P3 hardening: DNS-rebind pinning, require_auth=True default, Gate 4B PBT, REL-13 retry cap
Task Group 6, item C (Upstream Contribution, tasks 6.1–6.6).

Path A: upstream JailbrokenAI/wallbreaker is active (pushed 2026-07-18, not archived).
Base commit: bfd1d64 (upstream main HEAD at time of fork divergence).

Adds:
- UPSTREAM-PR.md: full PR body for the upstream contribution PR — finding table
  (3 Critical SEC-1/2/3, 13 High), Do-not-ship→Safe-to-ship narrative, references to
  the three pt-act/wallbreaker PRs (#1/#2/#3), agent_dashboard_harden fallback instructions,
  and verification steps.
- scripts/check-upstream-apply.sh: CI gate that imports SecurityMiddleware/ensure_launch_token
  (auth.py), PinnedEgressBackend/make_pinned_transport (egress_guard.py), and
  build_dashboard_registry (tool_policy.py) and exits 0 iff all symbols are present.

Security readiness (6.5/6.6):
- gh_token.txt confirmed gitignored (.gitignore:19 *_token.txt rule).
- No fork-only secrets in any committed file.
- SEC-1/2/3 (three Criticals) are the lead changes in PR #1 (commit fc4fd90).

Finding IDs covered: SEC-1/2/3/4/5/6/7/8/9/10/11/12, REL-1/2, RACE-1.
PBT gates: 22 Hypothesis properties across M0+P3 (test_security_properties.py).
TG1 (WS1 — Egress de-fragilization, items A/B):
- egress_guard.py: fail-closed self-check in make_pinned_transport (_force_missing_backend hook)
- egress_guard.py: two-tier policy docstring (advisory check_url vs enforcing PinnedEgressBackend)
- pyproject.toml: httpx pinned to >=0.27,<0.29 with private-attr comment
- tests/pbt/test_security_properties.py: 2 new unit tests (1.5, 1.6)
- pbt-properties.py: SP-3 test_pin_never_widens_policy + test_pinned_transport_fail_closed un-skipped

TG2 (WS2 — Test baseline & CI gate, item E):
- 31 corpus-offline tests marked xfail(strict=False), 7 marked skip
- .github/workflows/redteam-gate.yml: activated CI gate with httpx matrix,
  pytest -q -W error::ResourceWarning, PBT suite, ResourceWarning gate

TG3 (WS1 — Supply-chain corpus pinning, item D):
- library.lock.toml: SHA pins for P4RS3LT0NGV3/L1B3RT4S/ENI corpora
- parsel_engine.py: verify_corpus_sha + load_corpus_with_pin_check (fail-closed)
- cli.py: wallbreaker corpus verify + parsel verify alias
- tests/test_tg3_corpus.py: 6 focused tests
- pbt-properties.py: SP-4 test_corpus_sha_gate un-skipped

TG4 (WS2 — Frontend residual closure, item F):
- Runs.tsx: 624→295 lines (extracted RunDetailView.tsx with helpers)
- Findings.tsx: 602→373 lines (extracted FindingExpanded.tsx with renderFindingCell)
- Agent.tsx: 413→328 lines (extracted AgentTranscript.tsx: AttackerSwitch/Row/transcriptStatus)
- package.json: added check:line-counts script
- src/__tests__/subcomponents.test.tsx: 15 render+axe tests for extracted components
- vitest: 53/53 pass, tsc clean

TG5 (WS3 — Hardening toolkit extraction, item G):
- agent_dashboard_harden/__init__.py: pure re-export of SecurityMiddleware,
  ensure_launch_token, origin_is_same_site, egress guard, build_dashboard_registry
- agent_dashboard_harden/pbt_fixtures.py: 5 parameterizable PBT property factories
- agent_dashboard_harden/README.md: wiring example
- pyproject.toml: package include list updated
- tests/test_tg5_harden.py: 24 focused tests (identity + behavioral parity)
- pbt-properties.py: SP-1 test_extracted_access_control + SP-5 test_extracted_token_perms un-skipped

TG7 (WS4 — Trust frontier, item H):
- findings_log.py: Ed25519 signed append-only log (cryptography 49.0.0)
- judging.py: run_ensemble (concurrent judge ensemble) + run_ensemble_probe (test hook)
- tests/test_tg7_trust.py: 13 focused tests
- pbt-properties.py: SP-3 test_signed_log_tamper_evident + SP-5 test_ensemble_concurrency_bound un-skipped

TG6 (WS3 — Upstream contribution, item C): see upstream-contrib/security-remediation branch.

Final baseline: 1191 passed, 39 skipped, 31 xfailed. pytest -q exits 0.
pbt-properties.py: 8/8 active (0 skipped remain for this repo's TGs).
All 7 new Hypothesis PBT properties active across SP-1/SP-2/SP-3/SP-4/SP-5.
…en shim instructions

Issue 4 (PM verdict needs_revision): the PR body referenced agent_dashboard_harden
which lives on main (53c9ca2) but not on this branch. Replaced the shim-install
section with an accurate fallback: install from the fork's main directly. Added a
reviewer note explicitly stating agent_dashboard_harden is not part of this branch.
Issue 5 (major/process): updated current_focus.md to name roadmap-implementation
as complete, removed stale deferred items, set next resumption point.

Issue 1 (major): library.lock.toml — fixed corpus repo URLs (elder-plinius/,
not JailbrokenAI/); resolved real SHAs for locally-present corpora (UltraBr3aks
48f45de, ZetaLib a171235); online-only corpora (P4RS3LT0NGV3/L1B3RT4S/ENI) remain
UNRESOLVED with accurate URLs and notes. corpus verify now reports 2 OK + 3 UNRESOLVED.

Issue 2 (major): test_gemlib.py + test_fire_file.py — added skipif guards for
ZetaLib/UltraBr3aks corpus presence (14 tests guarded), matching the pattern used
for other corpus-dependent tests. pytest -q still exits 0: 1191 passed, 39 skipped,
31 xfailed.

Issue 3 (minor): RunDetailView.tsx 445 → 364 lines — extracted RunExpandedRow.tsx
(157 lines) containing InferenceExpanded + the expanded-row <tr> content.
Added RunExpandedRow.test.tsx (7 tests). Updated check:line-counts to cover both files.
Frontend: 60/60 vitest pass, tsc clean.

Issue 4 (minor): UPSTREAM-PR.md on upstream-contrib/security-remediation branch
updated — removed agent_dashboard_harden shim install instructions (that package
is on main/53c9ca2, not on the contrib branch); replaced with fork-install fallback
and a reviewer note clarifying branch scope. Committed to contrib branch separately.
…absent

_collect_seeds only emits zeta:/ultra: labels when gemlib.is_present is true; the
corpora are gitignored/runtime-fetched and absent on a cold CI runner, so
test_collect_seeds_includes_gem_corpora hard-failed. Guard it with skipif on the same
predicate (Issue-2 corpus-offline class). Verified: passes with corpora present, skips
when absent, full suite exits 0 under -W error::ResourceWarning.

Also: fix prompts.py:610 open().read() → context manager (ResourceWarning cleanup;
unclosed handle was GC-reaped but noisy under -W error::ResourceWarning).

Also: commit CHANGELOG.md (roadmap-implementation entry, staged since 9ec12b0).
…ed upstream PR body

- completion-report.md: producer record, criteria self-check, deviations, state writes
- UPSTREAM-PR.md: re-scoped to cover audit remediation + roadmap-implementation together
  (agent_dashboard_harden now on main, so the prior on-branch caveat is removed)
@pt-act

pt-act commented Aug 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review and approval @Gitanuj993 🙏

CI shows action_required — that's the standard fork workflow approval gate (first-time contributor). If a maintainer could approve the workflow run, the test suite should go green — our fork CI passes cleanly (1278 passed, 55 skipped, 31 xfailed).

I also opened #26 [mcp pin PR number] separately — mcp 2.0.0 dropped FastMCP and is breaking fresh installs for everyone. One-line dep pin, independent of this PR.

Ready to merge whenever you're comfortable. The capability track (engine intelligence + MCP server) will come as a follow-up PR as noted in the scope section.

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.

4 participants