The square you scan while setting up an authenticator app is not merely a convenient link. It contains the secret that lets the app generate your future login codes.
That distinction is easy to miss because a QR code looks like visual static. In a TOTP setup flow, though, the static is carrying a house key.
NTNT 0.5.6 was released today, and one of its smaller additions closes an avoidable gap in that flow. A new totp_qr_svg function turns a TOTP enrollment URI into an SVG inside the application. No QR website, image API, temporary file, or command-line program is involved.
The QR code is part of the secret
Time-based one-time passwords work because the authenticator and the server know the same secret. They combine it with the current time to calculate the short code you type at login. RFC 6238 says the prover and verifier must share that secret, and recommends protecting keys from unauthorized access.
The QR code is how the secret often crosses from one device to the other. The familiar otpauth:// enrollment URI includes an account label, issuer, settings, and a required Base32-encoded secret. Google Authenticator's key URI documentation shows the secret sitting plainly in the URI's query string.
If an application sends that URI to a remote QR-code service, the setup page may still look correct. The authenticator may scan it perfectly. But a third party has now received enough information to reproduce the same one-time codes. Two-factor authentication has acquired an uninvited third factor.
Local rendering does not solve every TOTP risk. The application still has to generate, store, display, confirm, and later verify the secret correctly. It must keep the enrollment response out of logs and caches. The user's screen can still be observed. What local rendering does is remove one network handoff that never needed to exist.
Give the authenticator a recognizable name
After scanning the QR, a person needs to recognize which service and account they just added. Set the service name and account label before drawing the image. Inside an existing local-auth enrollment handler, with std/auth initialized and the local user already provisioned:
import { begin_totp_enrollment, totp_qr_svg } from "std/auth"
let setup = begin_totp_enrollment(
"[email protected]",
map {
"issuer": "example.com",
"label": "[email protected]"
}
)?
let svg = totp_qr_svg(setup["uri"])?
This supplies example.com: [email protected] to the authenticator. The app controls the layout; it may show the service and account on separate lines instead. issuer is the service name, which can be a domain or a product name, and label is the account name, usually an email address. In a real handler, use the authenticated user's account identifier rather than hardcoding the demonstration address.
The enrollment API accepts both fields. If you omit label, it defaults to the local user's identifier. Set it explicitly when the displayed account name should differ from that identifier. These naming controls predate 0.5.6; the new part is rendering their enrollment URI locally.
If your application already manages the account's TOTP secret, the lower-level helper takes the account label and issuer directly:
import { totp_uri, totp_qr_svg } from "std/auth"
// secret is the account's existing TOTP secret, not a value from the browser.
let uri = totp_uri(secret, "[email protected]", "example.com")?
let svg = totp_qr_svg(uri)?
begin_totp_enrollment creates and stores a pending secret; totp_uri packages an existing one. Both produce a URI containing the account label and issuer, and totp_qr_svg turns that URI into SVG geometry without changing those values. The v0.5.6 release notes say the renderer makes no network requests, accepts valid TOTP URIs up to 2,048 UTF-8 bytes subject to QR capacity, and uses Q-level error correction with a four-module quiet zone.
The output does not interpolate the URI, account label, or secret into XML text or metadata. That is useful, but it does not make the image safe to publish. The black squares still encode the enrollment secret. A camera does not care whether the key appears in a <text> element or a maze of SVG paths.
I ran the release example
I downloaded the official Linux x64 archive and its checksum rather than testing whatever older binary happened to be installed. The checksum passed, and the binary identified itself as NTNT 0.5.6.
Then I linted and ran the tagged TOTP QR example, which uses the public RFC 6238 test secret rather than a real account. Lint reported no issues. The program produced a valid 26,266-byte SVG with a 69-by-69 view box.
I also ran the practical naming examples above, using a disposable in-memory account for enrollment and the public test secret for the existing-secret version. Assertions checked the issuer and account fields in the URI, an explicit label different from the login identifier, the default label when omitted, and successful local SVG rendering.
I also checked the XML source. Neither the literal otpauth:// URI nor the test-secret prefix appeared as readable text; the payload had been turned into geometry as documented. I did not enroll a real account or test camera decoding, so this was a focused rendering check, not an end-to-end authentication audit.
The rest of 0.5.6
The release is broader than QR codes. Concurrent tasks can now call application-defined and imported NTNT helpers. A new task_stats() function reports bounded retention gauges without exposing task contents. Interpreter fixes restore the caller's scope and contract state after function errors. TLS inspection, HTTPS monitoring probes, and SMTP TLS now use the host's CA store, which makes container CA configuration an upgrade requirement. The release also updates dependencies for published security advisories.
Those changes matter, especially the upgrade notes. The QR helper is simply the easiest one to see: a secret begins inside the application, becomes an image there, and does not take a sightseeing trip through somebody else's server first.