Extend the cross-system target guard from read paths to write paths - #8
Merged
Merged
Conversation
The 2026-08-06 S4D/100 vs S4H/400 incident had two SKILL.md-side causes that
the attach-lib fix alone could not close. Both are per-fenced-block, because
each fenced block runs as its own process:
(a) Dead session pin. Generator blocks resolved the AI-session pin into
$env:SAPDEV_SESSION_PATH, but cscript was launched from a LATER, separate
block -- so the export died with the generator, the attach lib found no env
var, and Strategy 3 (sole-connection) silently discarded the pin. Fixed by
resolving in the generator and baking into %%SESSION_PATH%% (Strategy 1),
which is a Const in the emitted VBS and survives the process boundary.
(b) Missing target expectation. Set-SapGuiTargetExpectation only works when it
runs in the process that launches cscript. Without it AssertSapGuiTarget has
nothing to enforce, so a deploy aimed at the wrong system writes there
instead of refusing.
Migrated all 40 affected skills (102 blocks: 58 dead pins + 44 unverified
write blocks), starting with the six flagship deploy skills (se38, se37, se11,
se24, se91, se21).
CI gate (scripts/check-consistency.mjs), gates 13 and 14 of the parallel-safe
attach contract. Shipped as WARN ratchets over the 102 offenders with a hard
error on the six flagship skills; promoted to unconditional hard errors once
both counts reached zero, per the bare-cscript / screen-baseline precedent.
Two gate corrections found during the migration:
* Tier-3-exempt VBS are skipped by gate 14 -- sap_check_gui_login_status.vbs
and sap_gui_security_warmup.vbs never include %%ATTACH_LIB_VBS%%, so the
expectation is inert there (cleared 6 false positives).
* Gate 13 accepts any child process, not just cscript. sap-update-addon
deliberately bridges the pin into a child powershell that resolves the
session itself; children inherit the environment, so that pin is live.
Deliberately not given an expectation, matching existing exclusions: sap-atc
and the read-only skills are not write-capable, and sap-stms keeps its own
TARGET-based PROD gate (an import's target SID differs from the pinned
connection -- its baked pin targets the domain-controller session).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The 2026-08-06 incident (S4D/100 vs S4H/400) happened because a skill resolves its SAP target twice, through two chains that can disagree:
Connect-SapRfcwalks pin → GUI-active → default → sole-profile, whileAttachSapSessionwalks hint →SAPDEV_SESSION_PATH→ sole-connection → refuse./sap-se38downloaded S4H'sZ_EXCEPTION_1while the RFC leg read S4D's — same program name, 7 lines apart, and neither output said which system it came from. Re-uploading the "fixed" download would have reverted a year of live edits on the other system.AssertSapGuiTarget+Set-SapGuiTargetExpectationalready closed the read path. This closes the write paths, where the same silent retarget is a bad write rather than a bad read.What changed
Two SKILL.md-side defects, both per-fenced-block — each fenced block runs as its own process, and that boundary is what breaks them.
(a) Dead session pin. Generator blocks resolved the AI-session pin into
$env:SAPDEV_SESSION_PATH, but cscript was launched from a later, separate block. The export died with the generator, the attach lib found no env var, and Strategy 3 (sole-connection) silently discarded the pin. Fixed by resolving in the generator and baking into%%SESSION_PATH%%(Strategy 1) — aConstin the emitted VBS, so it survives the process boundary.(b) Missing target expectation.
Set-SapGuiTargetExpectationonly works in the process that launches cscript. Without itAssertSapGuiTargethas nothing to enforce, and a deploy aimed at the wrong system writes there instead of refusing.40 skills migrated, 102 blocks (58 dead pins + 44 unverified write blocks), starting with the six flagship deploy skills: se38, se37, se11, se24, se91, se21.
CI gate
Gates 13 and 14 of the parallel-safe attach contract, in
scripts/check-consistency.mjs. Shipped as WARN ratchets over the 102 offenders with a hard error on the six flagship skills, then promoted to unconditional hard errors once both counts reached zero — the same path the bare-cscript and screen-baseline gates took.Two gate corrections that the migration surfaced (both are real semantics, not workarounds):
sap_check_gui_login_status.vbsandsap_gui_security_warmup.vbsnever include%%ATTACH_LIB_VBS%%, soAssertSapGuiTargetnever runs and the expectation is inert. Cleared 6 false positives (dev-init, gui-probe ×4, file-transfer's login probe)./sap-update-addondeliberately bridges the pin into a childpowershell -File …detect.ps1that resolves the session itself; children inherit the environment, so that pin is live.Reviewer notes
sap-atcand the read-only skills are not write-capable;sap-stmsis offSAFETY_GATE_SKILLSbecause an import's target SID differs from the pinned connection — its baked pin targets the domain-controller session, and the import target stays under its own W2/W3 PROD gate. Both are called out in the code comments.contributing/parallel_safe_session_attach.mdrecords the ratchet history (102 → 0), the promotion, and both gate refinements; gotchas 4 and 5 now point at gates 13/14.Verification
node scripts/check-consistency.mjs(what.github/workflows/validate.ymlruns) exits 0 with no warnings:No live SAP run — these are SKILL.md instruction blocks plus a static checker.
🤖 Generated with Claude Code