Skip to content

Scaffold permissionreset-smb, a read-only SMB-native tool for FlashArray - #38

Merged
robertcantu-pure merged 3 commits into
mainfrom
feat/smb-scaffold
Nov 13, 2026
Merged

robertcantu-pure merged 3 commits into
mainfrom
feat/smb-scaffold

Conversation

@robertcantu-pure

@robertcantu-pure robertcantu-pure commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Starts the smb/ tree proposed in docs/decision-flasharray-smb-20261107.md (sections 4, 5, 7).

Pre-release scaffold — writes nothing. It reads and decodes descriptors and validates plans; repair waits on the decision record's section 8 questions.

  • inspect: raw descriptors as JSON lines (owner/group SIDs, control flags, DACL/SACL ACEs with flags, base64 bytes); backup intent, reparse points not followed. A failed path gets a record with error (step + system reason) and win32_error; a missing privilege exits 1 with a message.
  • plan: validates and summarizes a plan file, with Go-compatible glob matching and rule selection.
  • Unit tests (smb/tests/), including the CLI's records and exit codes through a fake descriptor reader, and a new smb-windows CI job that builds, tests and smoke-runs the binary on windows-2022.
  • .gitignore covers the private public-scan denylist.
  • CLAUDE.md architecture entry; decision-record status updated.

Verification

Head is 0ed9d8d: e690ee2 rebased onto main at 2ebb967. The rebase changed only the root CLAUDE.md (main replaced the architecture tree, so the smb/ entry is now a short permissionreset-smb section). smb/ and the CI job are byte-identical to e690ee2.

Offline, on 0ed9d8d

  • Public scan (tree and origin/main..HEAD commits): clean.
  • go test ./...: pass.
  • .NET tests: 1593 passed on Linux (SDK 8). CI runs them on Windows.

Hardware, lab FlashArray, on e690ee2 (20bf2c3 run, benchmarks/qual-smb-inspect-20261109/RESULTS.md, and the e690ee2 rerun comment below)

  • Passed: read fidelity 13/13 with and without the SACL. Missing path: exit 1 with an error record and win32_error. Missing SeBackup/SeSecurity privilege: exit 1 with a message and no stack trace.
  • Still applies to 0ed9d8d: the rebase touched no smb/ file, so the descriptor path and the binary's sources are unchanged. A 0ed9d8d build is still a different artifact from the qualified one, and its checksum hasn't been recorded.
  • Not covered: effective access, identity mapping, traversal coverage, audit-event delivery. No write path exists, so nothing here qualifies one.

Known, pre-existing: xUnit skips some GoGlobTests vectors as duplicate test IDs (present on 20bf2c3 too); not addressed here.

🤖 Generated with Claude Code

@robertcantu-pure

Copy link
Copy Markdown
Collaborator Author

permissionreset-smb inspect: first hardware read, candidate 20bf2c3

These results are for 20bf2c3. This PR's head is now e690ee2, which this run doesn't cover; it needs its own run.

Candidate: permissionreset 20bf2c3 (PR #38), built with dotnet build smb/PermissionResetSmb.sln -c Release using SDK 8.0.427 (the version smb/global.json picks with latestFeature). permissionreset-smb.exe SHA-256 59B139D3896011EC49D7D4A9D4CDDE7370DEEF6FEAF51FE39595C71D944D3239. SDK 10.0.403 produced the same hash. Not modified or patched.

Environment: one FlashArray over SMB, read from a Windows Server 2022 client by an elevated domain account. The fixture is a 13-object tree (directories and files, 5 with audit ACEs, one protected DACL) built in a fresh directory and deleted afterwards. It's the same fixture as the PR #35 qualification: the descriptors before the run were byte-identical to that run's on 13 of 13 objects. Mapped principals only, no array configuration change, not a timing run.

Reference: a separate raw read with GetFileSecurityW, decoded with .NET RawSecurityDescriptor. The comparison checker was tested against a tampered record and caught a change in every field.

Passed

  • inspect with the SACL: exit 0, one record per object, sacl_read: true. On 13/13, the raw bytes equal GetFileSecurityW with all four parts. Owner, group, the control word and its flags, and every DACL and SACL ACE (type, flags including ID, mask, SID) match the reference decode. flag_names and inherited agree with the bits.
  • inspect --no-sacl: exit 0. There's no sacl key, sacl_read is false, and owner, group and DACL equal the full read. On 13/13, raw equals GetFileSecurityW without the SACL. The control word drops only the SACL bits, which is what the server returns for that request.
  • Missing path: exit 1 with one JSON error line and no crash. A valid path in the same run still gets its record.

Failed

  • Non-elevated run (no Administrators, no SeBackup or SeSecurity privilege): the process crashes with an unhandled Win32Exception (1300), "this account does not hold SeBackupPrivilege…", thrown from Win32Security.EnablePrivilege and called from Cli.Inspect. Exit code 0xE0434352, where exit 1 with an error line was expected. --no-sacl crashes the same way, because it also needs SeBackup.

Defects to fix (none affect read accuracy)

  1. Catch the privilege failure and exit 1 with its message.
  2. The per-path error is just "open". Include the Win32 code and message.
  3. Error records report sacl_read: true on a failed open. Report it as false, or leave it out, when nothing was read.

What this does and doesn't show: inspect's descriptor decoding matches an independent reader on real FlashArray SMB data, both with and without the SACL. It says nothing about effective access, identity mapping, traversal coverage, or whether audit events are actually delivered, and it doesn't qualify a later candidate. Detailed evidence is kept internally.

@robertcantu-pure

Copy link
Copy Markdown
Collaborator Author

permissionreset-smb inspect: hardware rerun, candidate e690ee2 (current head)

This follows the 20bf2c3 summary. e690ee2 sends reads through the new IDescriptorReader seam and changes the record shape (sacl_read is now optional, and there's a new win32_error), so read accuracy was checked again on the array as well as the fixed error cases.

Candidate: permissionreset e690ee2, unmodified, built with dotnet build smb/PermissionResetSmb.sln -c Release using SDK 8.0.427. permissionreset-smb.exe SHA-256 A865E64FC66BDD237AE71235FF249AFA1AC023C4FFD98C7284AC0C87AB9B8F90. Two separate clean builds gave the same hash.

Environment: the same as the 20bf2c3 run. One FlashArray over SMB, read from a Windows Server 2022 client by an elevated domain account, using the same 13-object fixture (5 with audit ACEs) built in a fresh directory and deleted afterwards. The descriptors before the run were identical to the PR #35 fixture's on 13/13. Mapped principals only, no array configuration change, not a timing run.

Passed

  • inspect with the SACL: exit 0, 13 records, empty stderr. On 13/13, raw equals GetFileSecurityW with all four parts. Owner, group, the control word and its flags, and every DACL and SACL ACE (type, flags including ID, mask, SID) match an independent .NET RawSecurityDescriptor decode. sacl_read is true.
  • inspect --no-sacl: exit 0, 13 records. There's no sacl key, sacl_read is false, and owner, group and DACL equal the full read. The control word drops only the SACL bits, which is what the server returns when the SACL isn't requested.
  • Unchanged from 20bf2c3: every success record has exactly path, descriptor and sacl_read, and is byte-identical to the 20bf2c3 records on 26/26 (13 objects × 2 modes) once the directory name is normalized.
  • Missing path, plus a valid file: exit 1 in both modes, empty stderr. The failed record is {"path":"…","error":"open: The system cannot find the file specified.","win32_error":2}, with no descriptor or sacl_read. The valid file's record is normal.
  • Neither SeBackup nor SeSecurity held (runas /trustlevel:0x20000): inspect and --no-sacl both exit 1 with empty stdout and no stack trace. stderr: permissionreset-smb: inspect: this account does not hold SeBackupPrivilege; run elevated as an account that does (Win32 error 1300). This is the crash 20bf2c3 had.
  • Elevated, SeSecurity removed, SeBackup kept: inspect exits 1, and the message names SeSecurityPrivilege (Win32 error 1300). --no-sacl exits 0 with one record, sacl_read: false.

All three defects found on 20bf2c3 are fixed as described.

Limits

  • The runas /trustlevel shell drops Administrators and both privileges but keeps the High integrity level. So the no-privilege case tests a missing privilege, not a medium-integrity process.
  • As before, this shows descriptor-read accuracy only. It doesn't show effective access, identity mapping, traversal coverage, or whether audit events are delivered.
  • It qualifies e690ee2 only. Any later push needs its own assessment.

Detailed evidence is kept internally.

robertcantu-pure and others added 3 commits November 12, 2026 23:25
Starts the smb/ tree named in the FlashArray SMB decision record: a .NET 8
solution that reads and decodes security descriptors over SMB and validates
plans. It writes nothing; the repair waits on the record's open questions.

- inspect: raw descriptors as JSON lines (owner, group, control flags,
  DACL/SACL ACEs, base64 bytes), opened with backup intent, no reparse follow.
- plan: validates and summarizes a plan file; Go-compatible glob matching
  and rule selection.
- Unit tests, and an smb-windows CI job that builds, tests and smoke-runs it
  on a hosted Windows runner.
- The decision record's status marks sections 4, 5 and 7 started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scan reads tools/public-scan/denylist.txt, usually a symlink to a
private checkout. Untracked, it was one git add -A from being published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The smb-windows smoke step ran an exe nothing had built: dotnet test
builds only what the tests reference. CI now builds the solution first,
tests with --no-build, and checks the exe exists before running it.

The first array read of inspect passed on read fidelity (13/13 with and
without the SACL) and found three error-path defects, fixed here:

- A missing SeBackupPrivilege or SeSecurityPrivilege escaped as an
  unhandled Win32Exception. inspect now prints the privilege and code on
  stderr, reads nothing and exits 1.
- A failed read's error was only the step name ("open"). It now keeps
  the system's reason, and win32_error carries the code.
- A failed read reported sacl_read: true. A record with no descriptor
  now has no sacl_read.

inspect reads through an IDescriptorReader seam, so the CLI's records
and exit codes are tested on any OS with a fake reader. The public
summary of the array run is in benchmarks/qual-smb-inspect-20261109/;
the error cases still need a rerun on this build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@robertcantu-pure
robertcantu-pure marked this pull request as ready for review November 12, 2026 23:26
@robertcantu-pure
robertcantu-pure merged commit 6db220e into main Nov 13, 2026
16 checks passed
@robertcantu-pure
robertcantu-pure deleted the feat/smb-scaffold branch November 13, 2026 00:11
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