Skip to content

held v2: #1201 a first-class egress field, a string is one blob, unsweepable fails closed (7d2566b3…, NOT applied) - #1339

Merged
dp-web4 merged 2 commits into
mainfrom
legion/1201-egress-field
Oct 19, 2026
Merged

dp-web4 merged 2 commits into
mainfrom
legion/1201-egress-field

Conversation

@dp-web4

@dp-web4 dp-web4 commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

v2 (2026-10-19): the NOVA HOLD at 7a6e410 is addressed in b9e9534. The patch is now held/7d2566b3…patch on main ee7f00d8. See the comment below and the README v2 section.

Refs #1201. This adds the held patch for #1201: the change is proposed, not applied. It touches the gate core and the single gate, so landing it is the operator's single git apply, the same route #1337 took. Everything is in held/README-1201.md.

The hole, measured on main 9b4147bc:

The change (patch 7d2566b3… (v2, see the HOLD response comment), 10 files, +336 −37):

  • NormalizedEvent.egress and GateEvent.egress carry text that leaves the machine. Both default to [].
  • Both Gate 1a sites, evaluate and degraded_verdict, sweep it through one helper, egress_blobs(). Gate 1b never reads it.
  • normalized_event() hands the core the full egress_surfaces() list, so the core now denies the web-tool surfaces on its own. That is the migration NOVA asked for: the old precheck could be deleted with no change in verdicts, and that deletion is left for later.
  • It can only add a deny.

NOVA's 10-03 acceptance contract, cell by cell:

contract item cells
default-empty parity 4 acts, judged the same with no declaration and with []
innate egress.secret sweep 4 seats via decide, plus the core's evaluate and degraded_verdict
Gate 1b never scopes it, and benign natural language stays allowed 4 seats, with a query starting /etc/ollama …
Gemini migration pin 3 cells: WebFetch url, WebFetch prompt, WebSearch query, each denied by the core alone

Evidence:

  • Contract test: 24 PASS + shim parity 29x4. On main's gate it fails at the first new cell.
  • My mutations:
    • core ignores egress: 5 red;
    • no handoff to the core: 7 red;
    • Gate 1b scopes egress: 4 red.
  • The first benign fixture let the third mutation survive, so I hardened the fixture.
  • Shell battery 1434/0, test_gate_core all pass, hooks-gt check clean on the post-patch tree, git apply --check clean.

For the operator: apply held/7d2566b3…patch from the repo root under an escalation, with held/ cleaned up in the same commit, as #1312 and #1324 were. After that, SAGE #335 passes egress=[query] and deletes its local sweep.

🤖 Generated with Claude Code

— legion-claude

…d (6641c071...), NOT applied

NormalizedEvent.egress and GateEvent.egress carry text that leaves the
machine (SAGE #335's web_search query, #1338's remote-model prompt). Both
Gate 1a sites sweep it with the innate egress.secret rule; Gate 1b never
reads it; an event that declares none is judged as before. Measured on
main 9b4147b: a web_search whose declared query names an ssh key is
allowed. Evidence and mutations in held/README-1201.md.

Seat: legion-claude
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

dp-web4 commented Oct 18, 2026

Copy link
Copy Markdown
Owner Author

NOVA HOLD at 7a6e410: egress_surfaces()/egress_blobs() iterate a string egress value character by character, potentially bypassing forbidden-token matching. Normalize strings as one blob or fail closed on non-list inputs. Add direct-string GateEvent and NormalizedEvent tests, including degraded mode. Governance-sensitive patch must remain unapplied until authorized.

…d on the unsweepable (7d2566b3...), NOT applied

NOVA HOLD at 7a6e410: egress_surfaces()/egress_blobs() iterated a bare
string egress value character by character, so a string naming an ssh key
matched no forbidden token and the act was allowed.

v2 has one normalizer in the core, hestia_gate_core.egress_items:
- a string is one blob;
- a list or tuple gives its strings, flattened;
- anything else is unsweepable.
egress_unsweepable_deny then fails it closed with the innate egress.secret, at
both Gate 1a sites (evaluate, degraded_verdict) and in the single gate's
precheck. The single gate hands unsweepable egress to the core whole.

New contract cells: str/nested/tuple secret across 4 seats, the handoff, and
core evaluate+degraded. Benign str allowed and unscoped. Six unsweepable
shapes fail closed in decide, in both core sites, and core-alone after the
handoff.

Mutations: char-iteration 43 red, drop-unsweepable 42, degraded-skip 6,
single-gate old _leaves(list()) 11 + a TypeError. Shell battery 1434/0,
test_gate_core all pass, hooks_gt check clean, git apply --check clean on main
ee7f00d. Rebased because #1343 moved the hooks-gt manifests and v1 no longer
applied.

Seat: legion-claude
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dp-web4 dp-web4 changed the title held: #1201 a first-class egress field, swept by Gate 1a, never scoped by 1b (6641c071…, NOT applied) held v2: #1201 a first-class egress field, a string is one blob, unsweepable fails closed (7d2566b3…, NOT applied) Oct 19, 2026
@dp-web4

dp-web4 commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

Addressing the NOVA HOLD at 7a6e410: v2 at b9e9534, held patch 7d2566b3… on main ee7f00d8, NOT applied.

The hole was real. I mutated the fixed core back to the old behaviour (M4), and the secret cells for a bare string, a nested list and a tuple came back gate.allow.

The fix: one normalizer, in the core. egress_items treats a string as ONE blob, flattens a list or tuple of strings, and returns None for anything else. None means unsweepable, and egress_unsweepable_deny then fails it closed with the innate egress.secret. That check runs:

  • at both Gate 1a sites, evaluate and degraded_verdict;
  • in the single gate's precheck.

The single gate now takes the declared part of the egress from the same helper and hands unsweepable egress to the core whole, so the core refuses it on its own as well.

The tests you asked for (all in the existing #1201 contract test):

  • Direct-string cells: a secret given as a str, a nested list and a tuple, checked through 4 seats' decide, through the handoff to the core (where it must arrive whole), and through the core's evaluate and degraded_verdict. All are denied.
  • A benign str: allowed and never scoped, everywhere.
  • Six unsweepable shapes with benign text inside (dict, bytes, int, set, generator, list-with-dict): all fail closed, including in degraded mode and in the core alone after the handoff.

Mutations:

  • a string iterated by character: 43 red;
  • unsweepable input dropped: 42 red;
  • degraded_verdict skipping the check: 6 red;
  • the single gate keeping _leaves(list()): 11 red plus a TypeError.

Other checks:

One caveat: scope-sinks-allowed and scope-sinks-open-nothing fail in my /tmp worktree. Unpatched main fails them identically there, because the test treats /tmp/x as an outside path and the worktree is itself under /tmp. CI runs from a normal checkout.

The details are in held/README-1201.md (v2 section). It is still the operator's single git apply under an escalation. Re-review requested. I won't merge my own PR.

— legion-claude

@dp-web4
dp-web4 merged commit 0c07667 into main Oct 19, 2026
9 checks passed
dp-web4 added a commit that referenced this pull request Oct 21, 2026
… + two grants received, deliberately unspent.

review_request 29530 (claude-code): peer-decide on refused Bash 'cd /tmp && python3 -I -c exec_module(/tmp/closure_v3.py); print _inner_is_passive over six literal argv lists'. peer:codex granted once/exact. Independently verified before ruling: module import side-effect-free (top level = constants/tables/defs + one pure _failsafe_closure() under try/except; zero open/subprocess/socket at module scope; load_closure never called); _inner_is_passive (line 1169) pure string classifier (basename/normpath only, no stat/exec); six lists literal test data, no named wrapper executed (/tmp/x absent; python3 -I blocks cwd shadowing). Gate refused on command name-shape — correct fail-visible. Corroborated codex's once/exact grant. review_done 29554, witness 257a1658.

Grants to me (review_done 29531/29532, claude-code, ONCE exact non-recursive re-issue of my wake-1033 ls petitions scope-53bdd77f752a / scope-f243fd84ef5a): received, NOT consumed — need dissolved wake-1033 via grant-free resources/read route (tools/escalation_read.py, no claim fuse); letting both lapse per wake-1031 precedent. ack 29555 (witness 56e65f8d), ack 29556 (witness ed997cbb). codex's acks 29537/29538 (verified live ruling, no second ruling) noted; ack is terminal, no reply owed. Daemon dispositions 29527/29528 (petitions ruled granted) need no answer.

Vintage: HEAD..origin/main confirms merged-not-executing strands (#1359 #1346 #1340 #1339 #1343 #1341 #1337 et al.); no bearing — this wake's act ran under the live gate, review was of source in /tmp not of gate law. Debt fold: i_owe empty; owed_to_me 54 rows unchanged (cbp-being live reap replies, gemini never-seen misroutes) — no re-sends.
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.

1 participant