Ardis Lu [Mon, 7 Sep 2026 05:55:32 +0000 (22:55 -0700)]
Refactor global `BROWSER` singleton into dependency injection
DI is required to enable concurrency in tests and should generally reduce flakiness. To complete the refactor and enable concurrency, the other global singleton `SERVER` must also be refactored, and a different approach to `assertConsole` must be used instead of mocking the global `console`.
Ardis Lu [Thu, 3 Sep 2026 05:33:45 +0000 (22:33 -0700)]
Fix broken regex (faulty `|` in version group)
This issue was not caught because `ENVPASS_VERSIONS` has only ever had one value, so the `|` in the regex was never used. The new test cases will catch this error when `ENVPASS_VERSIONS` has at least two values.
Ardis Lu [Wed, 19 Aug 2026 04:53:36 +0000 (21:53 -0700)]
Cast `parseEnv` result to `Record<string, string>`
Casting is required instead of a simple annotation because the default return value `NodeJS.Dict<string>` sets the value to `string | undefined`. The `undefined` scenario is impossible with `parseEnv`'s current implementation, so to handle the the `undefined` case results in an untestable code branch.
Ardis Lu [Sat, 15 Aug 2026 05:59:59 +0000 (22:59 -0700)]
Calculate client-side window expiration from `exp` query param
This change makes the client-side expiration in sync with the server-side expiration. Previously, if the client-side window is opened later than the server's start time, the expirations would be de-synced. It is also a maintenance burden to keep the duration constants in sync between the two.
Ardis Lu [Wed, 22 Jul 2026 07:35:47 +0000 (00:35 -0700)]
Manually narrow the return type of `parseEnv`
`Record<string, string>` clarifies that `value` can never be `undefined`, whereas `NodeJS.Dict<string>` allows `undefined`. This change allows the deletion of an unreachable and untestable code branch without raising type errors. The new return type matches the actual type used in the Node.js codebase [0].
Ardis Lu [Fri, 17 Jul 2026 07:02:32 +0000 (00:02 -0700)]
Refuse streaming/chunked requests
There is no practical reason the client must stream 32 bytes (should be able to send it in one chunk). This change reduces the attack surface and closes a potential DoS vector.
Ardis Lu [Thu, 16 Jul 2026 06:24:22 +0000 (23:24 -0700)]
Set `Content-Length` header on all responses
This value is easy to determine, so might as well make the response more useful and deterministic. Setting this header also lets the server drop the default `Transfer-Encoding: chunked` response header which could also be a miniscule performance improvement on clients.
Ardis Lu [Tue, 14 Jul 2026 06:13:27 +0000 (23:13 -0700)]
Do not emit end user-facing logs
Logs intended to improve the final UX should be a separate concern handled by the CLI or the library consumer. Library logs should only be related to debugging the library itself. This separation allows the code closest to the user to provide more tailored instructions (e.g., i18n) to the user.
Ardis Lu [Thu, 18 Jun 2026 06:05:03 +0000 (23:05 -0700)]
Move prf zeroing logic inside `finally`
This approach is more semantically aligned with what is meant to happen (wipe `prf` after it is broadcast, regardless of success or fail), and potentially better scheduling (previous approach allowed theoretical promises in the microtask queue injected after the `fetch` but before yielding to main thread?).
Ardis Lu [Mon, 8 Jun 2026 02:43:43 +0000 (19:43 -0700)]
Disable `package-manager-cache`
From `setup-node`'s `README`: "For workflows with elevated privileges or access to sensitive information, we recommend disabling automatic caching ... when caching is not required for secure operation."
Ardis Lu [Sun, 31 May 2026 06:07:37 +0000 (23:07 -0700)]
Narrow `ArrayBufferView` to `Uint8Array`
`ArrayBufferView` includes `DataView` which causes a type error when passed to the Web Crypto API. Although any typed array would work here, it is most common to use `Uint8Array` for this kind of data, so it should not cause any problems narrowing the required type.
Ardis Lu [Mon, 25 May 2026 00:30:25 +0000 (17:30 -0700)]
Use standard ES types
`BufferSource` is from web IDL (`lib.dom.d.ts`), which is not always imported. `ArrayBuffer` and `ArrayBufferView` are from the ES spec (`lib.es*.d.ts`) so they should always be available.
Ardis Lu [Mon, 25 May 2026 00:21:14 +0000 (17:21 -0700)]
Pass PRF as raw bytes instead of string
Avoiding string serialization is more performant and simpler, and more importantly reduces the places where this sensitive value is written to memory. By keeping the value in buffer we can immediately zero it out manually instead of waiting for garbage collection.
The value is still "leaked" via DevTools inspection and other points in the network stack, but that is out of the control of JS.
Ardis Lu [Sun, 24 May 2026 06:16:34 +0000 (23:16 -0700)]
Use custom `Envpass-Challenge` header
Separating `challenge` from `prf` allows more flexibility to change the format that `prf` is bridged (e.g., to `application/octet-stream`) and it is more consistent with how similar material is usually passed in other APIs.
Ardis Lu [Fri, 22 May 2026 06:21:00 +0000 (23:21 -0700)]
Set client-side security headers
The CSP `img-src 'none';` will block the empty data URL used for the favicon, so just remove it. The original purpose of the empty data URL was to prevent `favicon.ico` requests, but that is also accomplished by this CSP.
Ardis Lu [Fri, 22 May 2026 05:33:15 +0000 (22:33 -0700)]
Verify `Content-Type` header on POST
Setting this header to `application/json` forces the request to be CORS preflighted (i.e. not a "simple request"). This is further security hardening against cross-origin requests from a web browser.
Ardis Lu [Fri, 22 May 2026 05:20:18 +0000 (22:20 -0700)]
Verify `Origin` header on POST
This is security hardening against cross-origin requests. Although the server already does not send CORS headers (so web browsers should already block a cross-origin POST), there should still be a server-side `Origin` check as defense-in-depth.
Ardis Lu [Thu, 21 May 2026 05:59:14 +0000 (22:59 -0700)]
Only get passkey if actually required
Getting passkey is not required when `encrypt` is run on a file where all variables are already encrypted, or `decrypt` is run on a file where all variables are already decrypted.
Previously, the passkey flow would always initiate. Now, it will only initiate the first time it is required.
This change improves the UX in situations where `encrypt` or `decrypt` are called on a continuous basis. For example, when calling `envpass encrypt` in a `prestart` npm script hook.