Skip to content

Make a secret file usable on Windows, and stop two tests passing for the wrong reason - #107

Merged
parawanderer merged 1 commit into
mainfrom
fix/secret-files-on-windows
Aug 16, 2026
Merged

parawanderer merged 1 commit into
mainfrom
fix/secret-files-on-windows

Conversation

@parawanderer

Copy link
Copy Markdown
Owner

Branched from main, so it is independent of #106 and can go in either order.

Reading a secret from a file did not work on Windows at all

secrets.py refuses a file with the group or other read bits set and tells the user to run
chmod 600. Measured, not inferred:

mode: 0o100666
S_IRGRP set: True   S_IROTH set: True
after chmod 600: 0o100666 -> group/other readable: True

CPython synthesises st_mode as 0o666 for every regular file on Windows, and os.chmod cannot
clear those bits. So the guard was true of every file on the system — the safest way to pass a
secret was unusable on a platform this ships a binary for, and the advice it printed could not
change the answer.

The check is now skipped where the bits carry no information, rather than approximated. Real
permissions there are ACLs, which stat cannot see; reading them needs icacls or pywin32, which
is a large dependency for a guard that is advisory even on POSIX. Leaving it was not the safer
option
— it made the best input route impossible and pushed people to the environment variable,
which the module's own docstring ranks as worse. POSIX behaviour is untouched.

Two tests were passing for the wrong reason

This is the part worth reviewing. Of the three permission tests, two assert that an insecure file
is refused — and on Windows they passed, because every file was refused. Green ticks over an
assertion that could not fail.

They are skipped there now, with a reason that says so. The third failed for a second, unrelated
cause: pytest.raises(match=...) is a regex and a Windows path is full of backslashes, so it needs
re.escape.

test_device.py had the same shape — it asserts the stored device identity is readable by its
owner alone, which chmod cannot arrange there. Skipped, and device.py now says plainly that its
chmod is a no-op on Windows instead of leaving it to look like protection.

The marker lives in test/unittestutils.py beside skip_unless_unix, which is where this repo
already keeps these.

Result

Before After
Windows 8 failures 435 passed, 8 skipped, 0 failures
POSIX unchanged unchanged — os.name != "nt" and …

POSIX is unchanged by construction, and CI proves it: the four skipped tests are exactly the ones
that run there, so if the guard had been weakened they would go red.

Also: the handover doc back in line with the code

docs/android-import-handover.md had drifted into being confidently wrong.

  • §6's three outstanding items have shipped — the pin, the serial, the iPhone model.

  • The device-name ask is dropped, not pending. announce_device needs the
    com.apple.gs.idms.hb heartbeat token, which an account serialised before FindMy.py started
    keeping it does not have. Making it work means those users signing in again, which is not worth
    a re-login for a label.

  • §4 no longer asks for a Room migration. OwnedBeacons.content is already nullable in schema
    3, so a key-list tag is a row with accessory_json set and content NULL. No new column, no
    migration, and rule 1 does not come into it.

    The risk moved rather than vanished, and the note now names where: BeaconRepository already
    tolerates a NULL plist, but BeaconDataParser reads getOwnedBeaconInfo().content straight into
    an XML parse, and a custom tag has no naming record either — its display name is the name in
    its own mapping.

…the wrong reason

`secrets.py` refused any file with the group or other read bits set, and told the
user to run `chmod 600`. On Windows CPython synthesises st_mode as 0o666 for every
regular file and os.chmod cannot clear those bits, so the guard was true of every
file on the system: reading a secret from a file was impossible on a platform this
ships a binary for, and the advice offered could not change the answer. Measured,
not inferred - chmod 600 leaves the mode at 0o100666.

The check is skipped where the bits carry no information rather than approximated.
Real permissions on Windows are ACLs, which stat cannot see, and reading them needs
icacls or pywin32 - a large dependency for a guard that is advisory even on POSIX.
Leaving it was not the safer option: it made the safest input route unusable and
pushed people to the environment variable instead. POSIX behaviour is untouched.

Three permission tests are now skipped there, and **two of them were passing for the
wrong reason** - they assert that an insecure file is refused, which was true
because every file was. A skip that says so is worth more than a green tick that
means nothing. The third also failed for an unrelated reason: pytest.raises(match=)
is a regex and a Windows path is full of backslashes, so it now uses re.escape.

test_device.py had the same shape - it asserts the stored device identity is
readable by its owner alone, which chmod cannot arrange there. Skipped, and
device.py now says plainly that its chmod is a no-op on Windows rather than
leaving it to look like protection.

The marker lives in test/unittestutils.py beside skip_unless_unix, which is where
this repository already keeps that kind of thing.

Windows goes from 8 failures to none: 435 passed, 8 skipped. POSIX is unchanged by
construction - `os.name != "nt" and ...` - and CI proves it, because the four
skipped tests are the ones that run there.

Also brings docs/android-import-handover.md back in line with the code. §6's three
outstanding items have shipped, the device-name ask is dropped rather than pending
- announce_device needs the heartbeat token, which an account serialised before
FindMy.py kept it does not have - and §4 no longer asks for a Room migration,
because OwnedBeacons.content is already nullable. The risk there moved to the read
paths, and the note now says which one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@parawanderer
parawanderer merged commit 59a85b3 into main Aug 16, 2026
4 checks passed
saamdotexe pushed a commit to saamdotexe/OpenTagViewer that referenced this pull request Oct 22, 2026
…s-on-windows

Make a secret file usable on Windows, and stop two tests passing for the wrong reason
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