Naughty Keyboard is a tool for testers and developers. One shortcut types the next awkward value
into the field in front of you: null, a trailing space, a zero-width space, 30.02.2026, =1+1,
a name of 65 535 characters. They are the values real users and real data produce, and a form you
only ever tried with "John Smith" has never met them.
Every value comes with what it usually breaks and what a correct application does instead. When one of them breaks something, one more shortcut copies a report block you can paste straight into a ticket. A window and a command line, over one engine.
Where it works today. Typing into other windows works on Windows. On macOS and Linux the command line and the pack tools work, and a value reaches a field through the clipboard. Honest limits has the details.
⭐ If it found the bug your users would have found, leave a star. That is how the next tester who needs it finds out it exists.
What it can do
- One shortcut, one value -
Alt+Shift+Nclears the line and types the next value of the current pack into whatever window has the keyboard. Previous and restart are one chord away, and every chord can be changed. - Values that explain themselves - each one carries
breaks(what it usually breaks, and why) andexpect(what a correct application does). The pack file,nkb showand the report block all say so. - Invisible characters made visible - the palette draws one as a marker,
␣, instead of showing nothing, and counts every value four ways: graphemes, code points, bytes and UTF-16 units. - Know where you are - the palette shows the value that comes next and a counter of how far through the pack you are, and the value window searches the values of every pack at once.
- A bug report in one chord -
Alt+Shift+Bcopies a block for the last value: which value, from which version of the pack, typed as what, how large, and whether all of it arrived. The value is written in a form someone else can type back, never as the marker the palette drew. - Never takes the keyboard - the palette is a display driven by shortcuts and does not take focus, so the cursor never leaves the field you are testing.
- Clipboard mode - put values on the clipboard to paste yourself, for one window or for all of them.
- Packs you can check - a pack is a plain TOML file,
nkb lintchecks it against the format rules,nkb fmtputs it in canonical shape, andnkb new-packwrites the skeleton. - Made for scripts too -
nkb emitprints a pack as JSON, CSV or one value per line, escaped or raw, optionally as base64. Every command has exit codes you can branch on. - Offline, free, no telemetry - GPL-3.0, no account, and it opens no network connection.
Early development. The pack format is not frozen yet - it freezes with the first public release that ships packs. If the releases page is still empty, there is no release yet, and you build it from source as shown further down.
Every release has six archives, one for each program and system. nkb is the command line and
nkb-gui the palette, and VERSION below stands for the version, such as 0.1.0.
| System | Command line | Palette |
|---|---|---|
| Windows 10 or 11, x64 | nkb_VERSION_windows_amd64.zip |
nkb-gui_VERSION_windows_amd64.zip |
| macOS 11 or newer, Apple silicon | nkb_VERSION_macos_arm64.tar.gz |
nkb-gui_VERSION_macos_arm64.tar.gz |
| Linux x64 with glibc 2.35 or newer, such as Ubuntu 22.04 | nkb_VERSION_linux_amd64.tar.gz |
nkb-gui_VERSION_linux_amd64.tar.gz |
An archive holds the program, LICENSE, this README.md and THIRD-PARTY-NOTICES.txt, which carries
the licence of everything compiled into the program. There is no folder around them, so unpack an
archive into a folder of its own. Nothing is installed and nothing else is needed.
- Windows. The programs are signed, with a timestamp, so Windows names the publisher. Run
nkb.exefrom a terminal, or startnkb-gui.exe. - macOS. Each program is a bundle,
nkb.appornkb-gui.app, signed and notarised by Apple with the ticket stapled inside, and next to it is a link, so./nkbstill works. Keep the bundle and the link together. A copy of the program taken out of its bundle is refused, because the signature covers the bundle. - Linux. The programs are not signed, and the commands below say where they came from. The palette needs a desktop session, X11 or Wayland.
Typing into other windows works on Windows only for now, see Honest limits.
The four files whose names start with verify- are for checking a download, and they sit together at the
end of the list. verify-SHA256SUMS.txt holds the SHA-256 of every archive. The two .sigstore.json files
are statements signed when the release was made, and the GitHub CLI checks
them. Put the version in place of VERSION and the name of your archive in place of the example one:
gh release verify-asset vVERSION nkb-gui_VERSION_windows_amd64.zip -R donislawdev/Naughty-Keyboard
gh attestation verify nkb-gui_VERSION_windows_amd64.zip -R donislawdev/Naughty-Keyboard --predicate-type https://spdx.dev/Document/v2.3
gh attestation verify nkb-gui_VERSION_windows_amd64.zip -R donislawdev/Naughty-Keyboard --predicate-type https://spdx.dev/Document/v2.3 --bundle verify-naughty-keyboard_VERSION.sbom.sigstore.json
gh attestation verify nkb_VERSION_linux_amd64.tar.gz -R donislawdev/Naughty-Keyboard
gh attestation verify nkb_VERSION_linux_amd64.tar.gz -R donislawdev/Naughty-Keyboard --bundle verify-naughty-keyboard_VERSION.provenance.sigstore.json
- The first asks GitHub whether the file is one this release published. It works for every file of a release, because a published release here cannot change.
- The next two check the statement of what an archive holds, which is the bill of materials
verify-naughty-keyboard_VERSION.spdx.json. They work for all six archives, the first through GitHub and the second offline, with nothing but the file next to it. Both need--predicate-type: without itghasks for a statement of how the file was built, a signed archive has none, and the answer "no attestation found" looks like a broken release. - The last two check how an archive was built: by which workflow, from which commit, on which
runner. They work for the two Linux archives and the bill of materials, the files nothing signed. A
Windows or macOS archive was signed after the build, on the machine that holds the key, so its bytes
are not the bytes the build made, and its signature answers for it instead:
Get-AuthenticodeSignatureon Windows, andspctl -a -vvandxcrun stapler validateon the bundle on macOS.
SECURITY.md says what is signed, with which certificate, and why.
You need Rust 1.98 or newer.
git clone https://github.com/donislawdev/Naughty-Keyboard
cd Naughty-Keyboard
cargo build --release -p nkb-cli # the command line: target/release/nkb
cargo build --release -p nkb-gui # the palette: target/release/nkb-gui
Run the whole test suite with cargo test --workspace. Every pull request runs it on Windows, Linux
and macOS, together with Clippy, a check of the dependencies' licences and advisories, a static
analysis with Semgrep that fails on any finding of severity error or higher, and a release build of
nkb that is run against the shipped packs. What no automated run can see is the palette on a real
desktop: taking the keyboard, and a value arriving in somebody else's field.
The first start of nkb-gui opens a welcome window with a box to try the first value in, so the first
value does not land in somebody else's application. Closing it opens the palette and is remembered.
- Start
nkb-gui. The welcome window has a box to try the first value in. - Click the field you want to test, in any application.
- Press
Alt+Shift+N. The next value of the pack is typed into the field. - Look at what the application did with it, then press
Alt+Shift+Nfor the next value. When one breaks something,Alt+Shift+Bcopies a report block for the ticket.
The palette never takes the focus, so the cursor stays in the field the whole time. These are the chords that work today:
| Chord | What it does |
|---|---|
Alt+Shift+N |
Clear the line, then type the next value |
Alt+Shift+P |
Type the previous value |
Alt+Shift+0 |
Go back to the first value of the pack |
Alt+Shift+B |
Copy the report block for the last value |
Alt+Shift+Space |
Open the value window, to find a value or change the pack |
Alt+Shift+H |
Collapse the palette to its header, or expand it again |
Four more chords are registered and do nothing yet, and the shortcuts window marks them as not
available yet: typing the current value again, Alt+Shift+R, and marking the last result as works, a
problem or suspect, Alt+Shift+1, 2 and 3.
Every chord can be changed in the shortcuts window, or in settings.toml under [shortcuts]:
[shortcuts]
next-value = "Ctrl+Alt+Win+N"The file is %APPDATA%\Naughty Keyboard\settings.toml on Windows. The palette's own settings sit
beside it under [palette], for example clearing = "none" to stop it clearing the line first.
List what there is, with the full list under What is in the packs:
$ nkb packs
whitespace 12 values Whitespace
unicode-text 12 values Unicode and text
length-bombs 12 values Length bombs
...
9 of 9 packs loaded, 102 values.Read one in full. Values are printed escaped, because a value made of characters nobody can see has no other readable form:
$ nkb show whitespace
whitespace - Whitespace
Characters that take up space, or claim to, and are impossible to see in a form.
version 1.0, updated 2026-09-07, CC-BY-4.0, fields: any
trailing-space
Trailing space
value Kowalski\u0020
size 9 code points, 9 bytes
breaks Login and e-mail comparisons differ between browser and server; the account is created but cannot be found.
expect Trimmed everywhere or preserved everywhere - never one on sign-up and the other on sign-in.Take the values into a script or a test file. The account of what came out goes to standard error, so a redirected file holds values and nothing else:
$ nkb emit magic-values --format lines > magic-values.txt
nkb emit: 12 values, 47 code points, 47 bytes
$ head -4 magic-values.txt
no
null
true
NaNType one value into the focused field, on Windows. You get three seconds to click the field:
$ nkb send magic-values --index 2 --delay 3Add --clear to clear the current line first, which the palette does by default. send refuses rather
than guess. It types nothing while Ctrl, Alt, Shift or Win is held, nothing into a window running with
higher privileges than itself, and nothing when the keyboard focus is on a button, a link or a list
item, and it stops at once on Escape. Each of those ends with exit code 4.
Check a pack file you wrote. It reports everything in one pass:
$ nkb new-pack my-pack
Wrote my-pack.toml.
$ nkb lint my-pack.toml
0 errors, 0 warnings.
Checked 39 of 45 rules, 1 of them only in part. 5 not checked - run `nkb lint --explain` to see which and why.Table of contents
A form is tried with "John Smith", a date that exists and a number that fits. Then it meets real
data. Somebody pastes a name with a space on the end. Somebody's country is Norway, and its code is
NO. An export is opened in a spreadsheet. The bug reports come from users, one at a time, long after
the code was written.
The values that cause them are not secret and not clever. They are a short, stable list, and nobody types them in because typing them is tedious and nobody remembers all of them. Each one below is a real value from the built-in packs, with what the pack says it does:
| Value | What it does |
|---|---|
Kowalski and a trailing space |
Login and e-mail comparisons differ between browser and server, so the account is created but cannot be found |
=1+1 |
A spreadsheet treats it as a formula, and the exported file shows a computed result instead of the data |
no |
YAML 1.1 and several config parsers read it as boolean false, and NO is also the country code for Norway |
30.02.2026 |
Naive parsers roll it over to the second of March instead of refusing it |
| a single zero-width space | A field that looks empty but is not, so required-field validation passes |
| 128 characters that are 256 bytes | A field limited in bytes rejects a value the interface said was fine |
007 |
Codes lose their leading zeros the moment a spreadsheet treats the column as numeric |
| 65 535 characters | The limit of the most common text column type, where crossing it usually produces a raw database error |
Naughty Keyboard puts them one chord away, in a fixed order, so testing them becomes a minute and not a project.
Nine packs, 102 values, all built in. Every value says what it breaks and what a correct application does.
| Pack | Values | What it holds |
|---|---|---|
whitespace |
12 | Characters that take up space, or claim to, and are impossible to see in a form |
unicode-text |
12 | Characters that look innocent and break counting, comparison and display |
length-bombs |
12 | Values sized against common limits and against the gap between a character and a byte |
magic-values |
12 | Words a human reads as text and a parser reads as a boolean, an absent value or a number |
numbers-extreme |
12 | Numbers at the edges of their types, in foreign notations, and where precision runs out |
dates-impossible |
12 | Dates that do not exist, that are ambiguous, or that are written differently than the field assumes |
export-breakers |
12 | Values that pass validation and break only in the exported file |
locale-pl |
6 | Polish identifiers, formats and characters that global commercial products do not handle |
filenames-paths |
12 | Names a file system rejects, alters, or reads differently than the user meant |
A value too long to store is kept as a recipe and written out when it is sent, so length-bombs can
hold 100 000 characters in a few lines.
This section is here on purpose. A tool that quietly does less than it seems to is worse than one that says so.
- Typing into other windows works on Windows only. On macOS and Linux this build has no way to
register global shortcuts, so the palette cannot be driven from the keyboard there, and
nkb sendanswers that there is no way to deliver keystrokes yet. A value reaches a field through the clipboard instead, with the palette's Copy buttons and its clipboard switch. The command line and the pack tools,packs,show,emit,lint,fmtandnew-pack, do not touch the system and work everywhere. - You can write your own packs, but not load them yet.
nkb new-pack,nkb fmtandnkb lintall work on a file of your own. Reading a folder of your own packs into the palette and the command line is not wired up:nkb packsreports the folders for your own and your team's packs as not read. - The pack format is not frozen. It freezes with the first public release that ships packs.
nkb lintchecks 39 of its 45 rules today, andnkb lint --explainlists every one and why a few wait. - It does not look at the result. It types the value and does not read what the window shows, only the name of its program and the kind of control that has the keyboard. Whether the application coped is for you to judge. Marking a result on its own chord is not there yet, so the report block is the record.
- It will not type into everything.
nkb sendrefuses a window running with higher privileges than itself, because Windows would drop the keystrokes without a word, and a control where keys would act on the control and not on text. Where it cannot tell which kind of control has the focus, it sends. - A small catalogue. 102 values in nine packs is a start. It is chosen to be the values that cost the most to miss, and it is not a complete list of anything.
Naughty Keyboard is built for testing software you are responsible for, and the welcome window says so: use it only on systems you are allowed to test. The built-in packs hold values that real users and real data produce, with no exploit payloads in them. It is a way to find out how your own forms behave, not a way to attack someone else's.
That list is a plain file of strings under headings, and a good one. Naughty Keyboard is the rest of the job. It types the value into the field for you, one press at a time and in a fixed order. It says what each value usually breaks and what a correct application does. It shows an invisible character as a marker and counts the value four ways. It turns a finding into a report block for a ticket.
No. It opens no network connection, has no account and sends nothing anywhere.
The program is GPL-3.0-only. The built-in packs are CC-BY-4.0, which each pack states in its license
field.
In the rules nkb lint --explain lists, and in tests/packs/: files a validator must accept and files
it must refuse, each refused one naming the rule it breaks. A separate written specification is not in
this repository yet.
Yes. Every one of the ten, in the shortcuts window or in settings.toml. A chord that would take a key
from every application is refused with the reason.
A Rust workspace of six packages. The layers are enforced by the compiler and not by convention: reaching upwards is a compile error.
nkb-cli ---.
+--> nkb-adapters --> nkb-app --> nkb-core
nkb-gui ---' |
'--> nkb-sys (the operating system)
nkb-core depends on nothing. nkb-sys is the only package where unsafe compiles. The command line
links no graphical toolkit at all, and the palette uses Slint under its GPL-3.0
option. Neither program is a reduced version of the other.
The thing worth contributing is values: the awkward input that cost you an afternoon once, and that
belongs in a pack so nobody else loses it. A pack is a TOML file, and a value without a breaks field
is refused, because that field is what the catalogue exists for.
nkb new-pack my-pack
nkb lint my-pack.toml
nkb fmt my-pack.toml
The format is still moving and your own packs cannot be loaded yet, so for now open an issue with
the Suggest a value
form, which asks for the value, what it breaks and where you saw it, rather than a pull request. Bug
reports are welcome the same way. The example files in tests/packs/ show what a pack may and may not
look like.
Behaviour here follows the Code of Conduct, and security problems go through SECURITY.md rather than the issue tracker.
GPL-3.0-only, see LICENSE. The built-in packs are CC-BY-4.0. The monospace typeface the
palette ships, DejaVu Sans Mono, comes with its own licence in crates/nkb-gui/ui/fonts/. The edamame
icon is original artwork drawn for this project and is licensed like the rest of the repository.
Free, no account, no telemetry, and it opens no network connection.
Naughty Keyboard is built with an AI-assisted workflow.
Keywords: naughty strings alternative · test data for forms · edge case test data · input validation testing · unicode test strings · zero-width space test · trailing space bug · type test data into any field · QA test data tool · form testing shortcut · CSV formula injection test · impossible date test · string length limit test · exploratory testing tool
