Skip to content

emrg: a session id no directory can carry is refused by name - #2600

Closed
pm25coder wants to merge 1 commit into
argszero:masterfrom
pm25coder:fix/a-session-id-names-a-directory
Closed

pm25coder wants to merge 1 commit into
argszero:masterfrom
pm25coder:fix/a-session-id-names-a-directory

Conversation

@pm25coder

Copy link
Copy Markdown
Collaborator

Closes #2598

A session id becomes a directory, so an id no directory can carry is now refused before mkdir runs, by a sentence that names the name rather than the operating system.

What was measured before the change

On Windows Server 2022, master 6ddb7cc, with emrg.__file__ printed by the probe:

is_safe_session_id('con') -> True
Session.create_with_id('con', <agent>)
NotADirectoryError: [WinError 267] The directory name is invalid:
  <agent>\.emrg\sessions\con

CON, con.txt and com1 failed the same way; nul got one step further and failed on the memory subdirectory (FileNotFoundError [WinError 3]); and an id ending in a space or a dot is folded by Windows instead of refused, so it names a different directory than the id says (.. mkdir'd "successfully" and the first write into it raised FileNotFoundError).

Both doors already ask the predicate — an inbound client message via _refuse_an_unsafe_session_id, and server-side creation via _get_or_create_session (two production create_with_id call sites) — so the name passed both and failed later, deep in the filesystem. The host's symptom was a session that does not save, or a turn that dies, with a message about WinError 267.

The change

  • emrg/session.py — session_id_refusal() is the one writer of the verdict and of the sentence beside it; is_safe_session_id() is it returning None. Two refusals are added: a reserved device name (CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9, judged on the base name before the first ., case-insensitively), and an id whose last character is a space or a dot. UnsafeSessionId is raised by create_with_id before any directory is attempted.
  • emrg/server/daemon.py — the door passes the refusal sentence to the client. A device name is one path component, so it must not receive the path-escape sentence; a path escape still gets exactly (a session id is one path component), which an existing test pins.
  • tests/test_session_id_names_a_directory.py — 33 tests, both directions.

The refusals are platform-independent, on the predicate's existing ground: an id reaches a path on whichever platform runs the host, and a name that names nothing on one of them is not portable. The guard is the reserved set, not a prefix: console, con-artist, com10, com0 and lpt0 stay accepted, and a space or a CJK name is still an ordinary session name.

Verification, with the tree and interpreter named

  • New module plus the predicate's existing pin (tests/test_session_delete_path.py): 52 passed on e9daa97, emrg.__file__ = this checkout.
  • The daemon's two doors (tests/test_daemon.py -k session_id): 2 passed — the pinned invalid session id: '../../../evil' (a session id is one path component) is unchanged.
  • Mutation arms, each restored byte-exactly (git hash-object emrg/session.py = 791a485fef23d8a8431e20e42a63c0e3d2852a69 after each):
    • arm A — delete the device-name rule: 14 failed / 19 passed, including test_creating_a_session_with_a_device_name_is_refused_by_name and test_the_two_refusals_are_two_sentences.
    • arm B — delete the trailing space-or-dot rule: 4 failed / 29 passed; con and con. still fail under arm B because the device rule catches them, which is what makes the two arms discriminate.
  • Whole suite on e9daa97 in this checkout (emrg.__file__ printed): 5456 passed, 512 skipped in 927.79s.
  • Post-land audit of emrg: a scheduled task can be woken early by a when condition #2593, which this PR builds on: tests/test_wake.py + tests/test_scheduler.py on the same tree — 164 passed, 16 skipped.

What a reviewer should know

The running daemon renders the installed tree (~/.emrg/install/source), not this checkout, so this fix is not in effect for the running instance until the host refreshes the install; merging it changes the source, not the running body. Nothing here starts, stops or restarts a daemon, and no test in it touches the upgrade chain.

— cycle cyc20261026-204851

@pm25coder

Copy link
Copy Markdown
Collaborator Author

Closed in favour of #2599, which declares the same issue, was opened six minutes earlier, covers strictly more (its Session.delete logs the same reason instead of repeating a sentence) and already has both CI legs green. One issue is finished by one PR, so this branch is withdrawn rather than left standing beside a duplicate.

The readings below were taken independently on master 6ddb7cc, Windows Server 2022, with emrg.__file__ printed by the probe. They corroborate #2599's finding rather than proposing a second fix:

  • is_safe_session_id answered True for con, CON, con.txt, nul, com1, a and a.; Session.create_with_id('con', <agent>) then raised NotADirectoryError [WinError 267], and nul failed one step later on the memory subdirectory with FileNotFoundError [WinError 3].
  • My Task, 中文任务 and emrg-task created normally — the direction a reviewer should keep in view when reading a refusal.
  • Two mutation arms on this branch, each restored byte-exactly (git hash-object unchanged): deleting the device-name rule KILLED 14 tests; deleting the trailing space-or-dot rule KILLED 4. Four rather than six is the informative number — con and con. are caught by the device rule before the trailing rule runs, so a test that only asserts "refused" for those two cannot tell which rule fired.

That ordering is the one thing worth carrying into #2599's own tests.

@pm25coder pm25coder closed this Oct 26, 2026
@pm25coder
pm25coder deleted the fix/a-session-id-names-a-directory branch October 26, 2026 13:15
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.

emrg: a session id the filesystem cannot name reaches mkdir and fails as an OS error

1 participant