You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(scarecrow): three LUKS PINs — duress, decoy, and both at once
Scarecrow had one password. It raised a silent signal and opened a decoy
session, and it did not wipe anything, despite the docs and the wiki implying a
duress mechanism. Now there are three, each optional and independent:
duress erases the LUKS header of the configured device, silently
decoy a believable working session, erases nothing
duress+decoy erases the header AND opens the decoy, so the data is gone and
the screen still shows a working system
Design decisions worth keeping:
- **All three are verified on every attempt, with no early exit.** Stopping at
the first match would make the time taken depend on which slot matched, and
three Argon2 verifications at m=64MiB are slow enough for that to be
measurable. Two extra verifications on a machine about to be wiped is a cheap
price for not leaking which PIN was used.
- **Most destructive interpretation wins** if one password is enrolled in more
than one slot. Under duress is the wrong moment to resolve ambiguity in favour
of doing less.
- **Nothing is erased until --set-duress-device names a device.** Refusing to
guess is the point: erasing the header of a device nobody named is
unrecoverable and might be the wrong disk. The value is re-validated at wipe
time as well, so a mangled config file cannot become arbitrary arguments.
- **The wipe is the header, not the disk.** luksErase takes milliseconds and is
irreversible; overwriting a whole disk cannot finish before someone notices,
which defeats the purpose.
- **Every path is silent**, including the failures. No progress, no confirmation,
no error — not on success, not when cryptsetup is missing, not when no device
is set. An error mentioning cryptsetup on a login screen says exactly what was
attempted. A duress-only match prints `Login incorrect`, the same as a wrong
password, because a disk that will not unlock reads as a forgotten passphrase.
The README now states the limits plainly rather than implying more than the tool
does: the ciphertext survives on the platter and this is useless against someone
who imaged the disk first; it cannot help with a machine seized powered-on; the
decoy is a session, not disk-level deniability; and anyone who knows this tool
exists knows a duress PIN might. The deniability is that they cannot tell which
password you handed over — not that the mechanism is a secret, since it is in a
public repository.
fix(ci): --locked was being satisfied by a lockfile the previous step wrote
Three lockfiles — anti-ducky, arch-security-suite and scarecrow — had drifted
from their Cargo.toml: dependencies were declared and never resolved into the
lock. `cargo metadata --locked` fails on all three at the parent commit.
`cargo build --release --locked` should have caught that. It did not, because
the clippy step runs first *without* --locked, silently re-resolves, and
rewrites the lock — so --locked was checking a file generated moments earlier
rather than the one in the repository, and the released binaries were not built
from the committed lock at all. That is exactly the guarantee --locked exists to
provide, and it was not being provided.
Locks regenerated, and every cargo invocation before the build now carries
--locked, with an explicit `cargo metadata --locked` first so a stale lock fails
loudly at a step whose name says what is wrong.
Verified locally: `cargo clippy --target x86_64-unknown-linux-gnu --all-targets
--locked -- -D warnings` is clean for scarecrow, libre-otp, anti-ducky and
kernel-watcher. anti-evil-maid and arch-security-suite cannot be checked from
this machine — libudev-sys needs a system library that is not present when
cross-checking from Windows — so CI remains the check for those two.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>