2026-08-10 14:58:43 -03:00
2026-08-10 14:25:30 -03:00
2026-08-10 14:25:30 -03:00
2026-08-10 14:25:30 -03:00
2026-08-10 14:25:30 -03:00
2026-08-10 14:58:43 -03:00
2026-08-10 14:32:58 -03:00
2026-08-10 14:32:58 -03:00
2026-08-10 14:32:58 -03:00

logsdu

A Firefox extension that logs you into an academic portal whose sign-in form is three fields -- registration number, birth date and national ID -- rather than the usual user and password.

That shape defeats password managers. The form is marked autocomplete="off", none of the three inputs is a password field, and Bitwarden, Firefox and Chrome all decline to remember it. Since the values never change and the institution does not let you pick a different login method, the only option left is copying three values by hand, every time.

logsdu stores them once and fills them in. By default it also presses "Entrar", so the normal case is zero clicks.

Install

Dependencies are managed with pnpm, never npm (see "Why pnpm" below). Node 22+ ships corepack, so pnpm does not have to be installed globally.

corepack pnpm install     # first time, or after a dependency change
make xpi                  # typecheck, bundle, and package logsdu-<version>.xpi

Then install it permanently. Firefox will not load an unsigned add-on unless you tell it to, and only the ESR, Developer Edition and Nightly builds accept being told:

  1. Open about:config, accept the warning
  2. Set xpinstall.signatures.required to false
  3. Open about:addons, click the gear icon, choose Install Add-on From File, and pick logsdu-<version>.xpi

It survives restarts. On release Firefox that pref is ignored -- see "Release Firefox" below.

Finally, open the extension's options page, fill in the four values, and save.

The four values

Field Example Notes
Portal address https://portal.example.br/ Only the origin matters; the path is ignored
Registration number 2000101010 Digits only
Birth date 01/01/2000 Reformatted as you type
National ID 000.000.000-00 Reformatted as you type

Paste raw digits if you like -- the options page inserts the separators, because the portal's input masks expect the values in exactly that shape.

The portal address is configuration, not code. No institution is named anywhere in the extension: not in the manifest, not in the source, not in the build output. Be clear about what that does and does not buy you. It stops someone who reads the extension from learning which portal it is for. It does not hide anything from someone who can read the extension's storage -- and that is the same access that would expose your national ID and birth date anyway.

How it behaves

  • Fills and submits on the login page, with no interaction.
  • At most one automatic submit per hour. After an attempt, the extension drops back to filling only, so a wrong value cannot resubmit itself on every page load and lock you out. Correct the values and save; saving clears the timer, so the next visit tries again immediately.
  • Logging out keeps you logged out. Clicking the portal's logout control suppresses the automatic submit for five minutes -- otherwise the logout redirect lands on the login page and you would be signed straight back in.
  • When it fills without submitting, a small note at the bottom of the page says why. Click it to dismiss.
  • The toolbar popup has a Preencher agora button that fills without submitting, for when you want to check the values before sending them.
  • Automatic submission can be turned off entirely in the options.

How the values are stored

In storage.local: private to this browser profile, never synced, never sent anywhere. That is the same protection a browser-saved password gets, and it has the same limit -- anyone with your unlocked account can read it. If that is not good enough for your threat model, this extension is the wrong tool.

Release Firefox

xpinstall.signatures.required only works on ESR, Developer Edition and Nightly. Release Firefox ignores it and refuses unsigned add-ons outright, so there the same .xpi has to be signed first: upload it to addons.mozilla.org as an unlisted add-on. Signing is automated -- nothing is published publicly or reviewed by hand -- and you install the signed file it hands back.

The browser_specific_settings.gecko.id in the manifest is what gives the add-on a stable identity across both routes, so settings survive an upgrade from one to the other.

Chrome

Not yet. The source deliberately avoids anything Firefox-specific: it uses the chrome.* namespace, Manifest V3, and no APIs Chrome lacks, so a Chrome build should be a manifest question rather than a rewrite. It has not been tried, so do not assume it works.

Development

corepack pnpm install
make test                 # unit tests
make                      # typecheck and bundle into build/
corepack pnpm run dev     # rebuild on change
make clean

While iterating, reinstalling an .xpi for every edit is tedious. Load the unpacked directory instead: about:debugging -> This Firefox -> Load Temporary Add-on -> build/manifest.json, then press Reload there after each rebuild. That copy disappears on restart, which is the point -- it is for development, not for daily use.

corepack pnpm run dev watches and rebuilds, static files included, but Firefox still needs the Reload click to pick anything up.

Layout

Path Role
src/manifest.json MV3 manifest. Names no site
src/content.ts Isolated world. Decides whether to act, then delegates
src/injected.ts Page world. Does the actual filling and clicking
src/portal.ts Every selector the extension knows about the form
src/config.ts Stored values, the rate limit and the logout cooldown
src/format.ts Input normalisers for the three masked fields
src/options.*, src/popup.* The two bits of UI

Why two scripts instead of one

The portal drives its inputs with Inputmask, which replaces each element's value property with its own accessor. A content script assigning input.value from the isolated world writes through Xrays to the native setter and skips that accessor: the field looks right on screen, but the mask's buffer is unchanged, and the page's submit handler reads the stale buffer back out through jQuery. So the filling happens inside the page, through the page's own jQuery and Inputmask.

Submitting is a real click on the button, never form.submit(). The portal intercepts the submit event, cancels it, and posts by AJAX with a CSRF token taken from a meta tag. form.submit() would bypass that handler and lose the token; a click reproduces exactly what a person pressing "Entrar" does.

Why the content script matches every URL

The address is configured at runtime, so it cannot also be a manifest match pattern -- putting it there is exactly what would name the institution in the shipped code. The script therefore loads everywhere and stops immediately unless the page's origin equals the configured one. Origins are compared whole, so portal.example.br.evil.tld does not match.

The honest cost: the extension holds read access to every page you visit. The alternative -- registering the content script at runtime with scripting.registerContentScripts and an optional host permission -- avoids that at the price of a background script and a permission prompt.

Why pnpm, not npm

Do not run npm install or npm ci in this repo. This project is developed on ZFS, and npm's installer renames many directories in parallel while hoisting and deduping, which ZFS intermittently fails with:

npm error code ENOTEMPTY
npm error syscall rename

pnpm hard-links packages from a global content-addressable store and does not perform that rename dance, so it is unaffected. Only npm install / npm ci are the problem -- running scripts through npm is fine, since that just spawns tsc and esbuild. The lockfile is pnpm-lock.yaml; there is no package-lock.json and none should be created.

Licence

GPL-3.0-or-later. See LICENSE.

Description
Auto login into university portal
Readme GPL-3.0 285 KiB
release 0.3.2 Latest
2026-08-11 11:23:04 -03:00
Languages
TypeScript 68.8%
JavaScript 13.1%
Makefile 10.4%
HTML 5.1%
CSS 2.6%