Skip to content

docs(change): restrict CR-OC-001C to present, protected DACLs (§10.2b) - #130

Merged
coreytshaffer merged 3 commits into
mainfrom
cr-oc-001c-protected-dacl-amendment
Aug 1, 2026
Merged

coreytshaffer merged 3 commits into
mainfrom
cr-oc-001c-protected-dacl-amendment

Conversation

@coreytshaffer

Copy link
Copy Markdown
Owner

CR-OC-001C §10.2b — restrict execution to present, protected DACLs

Documentation only. Three paths, no code, tests, workflow, dependency, fixture, or configuration change:

docs/change/requests/CR-OC-001C-…-executor.md
docs/current_backlog.md
docs/change/change_log.md

Branched from origin/main at 8e19cdf. Merge is not authorized yet.

The change

CR-OC-001C now supports only targets whose pre-mutation DACL is present, non-NULL, and protected from inheritance (SE_DACL_PROTECTED set). Any absent, NULL, or inheritance-enabled DACL is refused as metadata_precondition_failed with outcome not_attempted, before temporary-file creation and before any replacement attempt.

This narrows the supported profile rather than weakening the invariant. Unchanged:

  • the exact owner, DACL-state, control-bit, AclRevision, ACE-count, ACE-order, and complete-ACE-byte comparison of §10.1 — nothing is relaxed, made unordered, made partial, or replaced by an effective-permissions rule;
  • the §10.1a SE_DACL_AUTO_INHERITED monotonic rule;
  • post-verification (§12), which remains mandatory and authoritative. The narrowed profile is a precondition, never a substitute for checking.

Unsupported targets are not normalized, repaired, protected automatically, or attempted. The executor never modifies a security descriptor to make a target supportable — it refuses.

The amendment does not claim Microsoft guarantees exact preservation for all protected DACLs.

Discovery record

30689442321:
  unprotected target differed in
  dacl_auto_inherited, ace_count, ace_bytes_or_order

30690450931:
  paired protected control did not fail, but was under-asserted

30691068391:
  strengthened protected control passed all fixture, result,
  outcome, exact-byte, and exact-metadata assertions
  unprotected control reproduced the same three differences

The middle run is recorded deliberately. The protected control's absence from the failure list was not evidence. As first written it failed only on a metadata label beyond dacl_auto_inherited, so a refusal such as temp_creation_failed accompanied by no metadata differences would also have passed it — and the recorded result property was unrecoverable, because the mandatory command failed before the gate or summary exposed it and no artifact is uploaded.

The control was strengthened to require simultaneously: a protected and nonempty fixture, reason_code == ok, outcome replacement_verified, target bytes equal to the proposed bytes, and metadata differences of none or dacl_auto_inherited alone.

What run 30691068391 establishes, stated narrowly

On the hosted runner, the unprotected profile violated the exact invariant, while the present-and-protected profile completed a verified replacement under the exact same comparison.

What it does not establish

It does not prove that inheritance recomputation is the underlying Windows mechanism. The diagnostic reported field labels only; it did not establish that every new or changed ACE was inherited, nor that effective access was unchanged. Microsoft documents ReplaceFileW as preserving the DACL and separately describes the operation as merging attribute and ACL information into the replacement file, without promising byte-identical ACE enumeration in every inheritance environment. The governance conclusion does not depend on which explanation is correct.

The hosted environment is recorded as bounded evidence, not a universal Windows guarantee:

Windows Server 2025 · build 10.0.26100 · windows-2025-vs2026 · Python 3.12.10 · NTFS

Why not an effective-permissions rule

Materially harder to prove safely than exact equality: ACE order affects access decisions, and common effective-rights helpers omit owner rights, privileges, logon-session groups, resource-manager policy, and some inherited-deny cases. Exact equality over a narrower profile is the conservative direction.

pre_dacl_nonempty is not a production requirement

It was a diagnostic validity control — its only job was to prove the hosted success was not a trivial empty-ACL comparison. The executor does not require a nonempty DACL: a present, non-NULL, protected DACL with zero ACEs remains supported, and §10.1's three-state classification still distinguishes present-empty from NULL and absent. §10.2b says so explicitly so a later reading cannot promote it.

§11 carry-over language revised

§10.2's DACL carry-over bullet and §11.2's merge language no longer imply that ReplaceFileW alone establishes general exact preservation. Both now state that documentation is trusted to choose the primitive, never to skip the check; that hosted evidence supports preservation only within the protected profile; and that post-verification remains the deciding evidence.

Obligations and mutants

T21 is revised, not supplemented — obligations stay at 36. It now covers four parts: an unprotected target refuses before temporary-file creation or ReplaceFileW with original bytes unchanged and no artifacts remaining; seam-injected absent and NULL DACL states refuse identically; a present and protected target completes with ok, replacement_verified, exact proposed bytes, and the full exact metadata invariant; and existing injected post-replacement differences still produce metadata_preservation_failed.

M31 — omitting the present-and-protected precondition, or treating an inheritance-enabled target as supported. Killer: T21[W]'s unprotected-target test. Windows-scoped by necessity, since its killer asserts refusal against a genuine NTFS descriptor and a real mechanism call log.

mutants: 31 total — 21 Windows, 10 Ubuntu

Verified against the tables: §20 and §25.6 each list 31 rows; 19 Windows-only + 2 dual-venue (M13, M27) = 21 Windows-participating, 10 Ubuntu-only.

§21 and §25.7 acceptance now additionally require: every healthy replacement case runs within the supported profile; T35[W] confirms the supported pre-state before replacement; every other participating component is exactly preserved; absent, NULL, and unprotected profiles fail before mutation; and M31 is killed cleanly.

One pre-existing correction, flagged

The §25.5 heading read "34 test obligations" while its table has listed 36 since the §10.1a amendment. Corrected here. This was not part of the ruling — it is a stale count found inside an authorized path, and I am flagging it rather than folding it in silently.

Sections touched

§1 (amendment lineage), §10.2, §10.2b (new), §11.2, §19 T21 and T35, §20, §21, §25.5, §25.6, §25.7.

What this does not do

No production or test change belongs on this branch, and none is present. The implementation on draft PR #128 remains unmerged; the production profile gate and M31 remain outstanding work there. Merge authority for this amendment has not been granted.

🤖 Generated with Claude Code

Three hosted Windows CI runs established that a successful ReplaceFileW does
not preserve the required invariant on inheritance-enabled targets. A new
section 10.2b narrows the supported profile rather than weakening the
invariant.

CR-OC-001C now supports only targets whose pre-mutation DACL is present,
non-NULL, and protected from inheritance. Absent, NULL, and
inheritance-enabled DACLs are refused as metadata_precondition_failed with
outcome not_attempted, before temporary-file creation and before any
replacement attempt.

Unchanged: the exact owner, DACL-state, control-bit, revision, ACE-count,
ordered-ACE, and complete-ACE-byte comparison; the SE_DACL_AUTO_INHERITED
monotonic rule; and the mandatory, authoritative post-verification.
Unsupported targets are never normalized, repaired, protected automatically,
or attempted.

Discovery record:

  30689442321  unprotected target differed in dacl_auto_inherited,
               ace_count, ace_bytes_or_order
  30690450931  paired protected control did not fail, but was
               under-asserted -- recorded deliberately, because absence
               from a failure list is not evidence
  30691068391  strengthened protected control passed all fixture, result,
               outcome, exact-byte, and exact-metadata assertions; the
               unprotected control reproduced the same three differences

What that establishes is narrow: on the hosted runner the unprotected
profile violated the exact invariant while the present-and-protected profile
completed a verified replacement under the same comparison. It does not
prove inheritance recomputation is the underlying Windows mechanism, and the
environment (Windows Server 2025, build 10.0.26100, windows-2025-vs2026,
Python 3.12.10, NTFS) is bounded evidence, not a universal guarantee.

An effective-permissions rule was rejected: ACE order affects access
decisions, and common effective-rights helpers omit owner rights,
privileges, logon-session groups, resource-manager policy, and some
inherited-deny cases.

pre_dacl_nonempty was a diagnostic validity control, not a production
requirement; a present, non-NULL, protected DACL with zero ACEs remains
supported.

Section 10.2's carry-over bullet and section 11.2's merge language no longer
imply that ReplaceFileW alone establishes general preservation.

T21 is revised into four parts rather than adding a thirty-seventh
obligation, so obligations stay at 36 while mutants move from 30 to 31
(21 Windows, 10 Ubuntu). M31 is omitting the present-and-protected
precondition. Sections 21 and 25.7 add the corresponding acceptance
requirements.

Also corrects a pre-existing stale count: the section 25.5 heading said 34
test obligations while its table has listed 36 since the 10.1a amendment.

Documentation only. No code, tests, workflow, dependency, fixture, or
configuration changed. The production profile gate and M31 remain
outstanding work on draft PR #128.

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

netlify Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for poetic-quokka-0fd859 ready!

Name Link
🔨 Latest commit 0fca72f
🔍 Latest deploy log https://app.netlify.com/projects/poetic-quokka-0fd859/deploys/6a6db4dca6b04300080c0294
😎 Deploy Preview https://deploy-preview-130--poetic-quokka-0fd859.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

coreytshaffer and others added 2 commits August 1, 2026 01:50
1. T20 part 3 still described an unrestricted healthy replacement -- "an
   ordinary target created by this process reaches replacement_verified" --
   which under section 10.2b either permitted or required a healthy result
   without establishing the supported DACL profile. It now reads: a target
   created by this process, then placed into a present, non-NULL, protected
   DACL profile without changing its owner, reaches replacement_verified,
   and post owner equals pre owner. The section 10.2a change-log lineage
   entry carries the same qualifier, with a note that the original wording
   would be ambiguous under the amended contract.

2. Section 1's implementation status said "Proposed, not authorized", which
   has been false since implementation was explicitly authorized. It now
   records that implementation is authorized and in progress on draft
   PR #128; not merged, not accepted, not runtime-integrated; and not yet
   conformant with section 10.2b, since the supported-profile gate, revised
   fixtures, T21 evidence, and M31 remain pending. The adjacent authority
   and approval-gate bullets are adjusted so they no longer deny an
   authority that was in fact granted, while still recording that it came
   from a separate explicit approval bounded to the section 25.1 seven-path
   allowlist, and that it is not merge authority.

3. The gate order is now explicit: the supported-profile gate is evaluated
   immediately after validating the pre-mutation security snapshot and
   before the TokenOwner ownership gate, despite the order the two bullets
   are written in. That gives T21's absent, NULL, and unprotected cases a
   precise cessation point and avoids querying the token's default owner
   for a profile already known to be unsupported.

Section 22 needed no change: its "not authorized" line is scoped to the
requirements-era five-path list and is immediately followed by the existing
annotation recording that section 25.1 supersedes it. The backlog needed no
change: its section 10.2a summary does not enumerate T20's three parts and
never carried the ambiguous phrase.

Documentation only, within the same three-path boundary. No code, tests,
workflow, dependency, fixture, or configuration changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lineage sentence said each amendment was "merged as a documentation-only
change before the implementation was corrected to match". That is true of
10.1a and 10.2a but not of 10.2b, whose implementation correction has not
happened yet -- as the very next status bullet correctly states.

Replaced with future-neutral wording: implementation is corrected only after
the corresponding documentation-only amendment merges, and the 10.2b
implementation correction remains pending on draft PR #128.

Documentation only, within the same three-path boundary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coreytshaffer
coreytshaffer marked this pull request as ready for review August 1, 2026 08:58
@coreytshaffer
coreytshaffer merged commit 5e41d6f into main Aug 1, 2026
7 checks passed
coreytshaffer added a commit that referenced this pull request Aug 1, 2026
Implements the amendment merged as 5e41d6f (PR #130).

Production (triage_core/mediated_executor.py):

  The supported-profile gate is evaluated immediately after the pre-mutation
  security snapshot validates and BEFORE the TokenOwner ownership gate, so an
  already-unsupported profile ceases without a needless token query. A DACL
  that is absent, NULL, or not protected from inheritance is refused as
  metadata_precondition_failed / not_attempted, before temporary-file creation
  and before ReplaceFileW.

  The executor only INSPECTS and REFUSES. It never protects, normalizes,
  repairs, or otherwise alters a target's DACL to make it eligible.

  ACE count deliberately does not participate: a present, non-NULL, protected
  DACL with zero ACEs is supported.

Tests (tests/test_mediated_executor.py):

  Healthy Windows fixtures are placed into the supported profile by
  construction via workspace(protect=True) and prepare_supported_target(),
  which disable inheritance while copying the effective ACEs in as explicit
  entries and leave the owner untouched. Fixture preparation lives entirely in
  the tests. A fixture that fails to protect raises rather than skipping.

  T20 part 3 is now a supported-profile control asserting the pre-state is
  present and protected, and that post owner equals pre owner.

  T21 gains: a genuine unprotected NTFS target refusing before any mutation
  with the cessation point at capture_security, no temp/replace/write/delete,
  no token query, bytes intact, no artifacts, and the DACL still unprotected
  afterwards (the executor did not repair it); seam-injected absent and NULL
  states refusing identically; a present-protected zero-ACE DACL proving
  pre_dacl_nonempty never leaked into production policy; and the
  supported-profile control requiring ok, replacement_verified, exact proposed
  bytes, and the complete metadata invariant.

  T35W now confirms the supported profile before replacing. With a protected
  fixture the runner exhibits True->True rather than False->True -- both
  accepted under 10.1a, which is why that section refused to require the
  normalization.

  The two temporary diagnostic tests are converted into their contracted T21
  homes rather than left as parallel controls, and T35W now shares the single
  difference classifier so the two cannot drift.

Evidence, observed rather than carried forward:

  Windows focused    149 passed / 1 optional skip
  Windows mandatory  173 passed / 1 deselected / 0 skipped, gate PASS
  Ubuntu neutral      96 passed / 54 skipped, guards 24 passed
  Full repository   1664 passed / 6 skipped
  Mutants            31/31 killed (21 Windows, 10 Ubuntu), zero survivors

  All 31 anchors pre-validated as unique and CRLF-normalised before any cycle;
  byte-exact restoration and a clean 640-file manifest before and after every
  Ubuntu cycle; JUnit privacy scan reports zero SIDs, access masks, and SDDL.

No dependency, pyproject.toml, runtime-integration, or eighth-path change.
The six named modules remain unmodified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coreytshaffer
coreytshaffer deleted the cr-oc-001c-protected-dacl-amendment branch August 1, 2026 10:05
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