per-user linux accounts are not optional any more
OFFICER_OS_USERS is gone. The platform behaves as it always would have with the flag on, and there is nothing to enable. Six conditionals, five of which were dead weight — provisionOsAccount, deprovisionOsAccount and the create/delete paths each opened with an early "not enabled on this server" return, and the API told the frontend whether to render the Linux controls at all. Those go, along with the 'disabled' DeprovisionResult stage, which nothing can produce now. The sixth is the one with teeth. assertSecretsClosed opened with `if (!OS_USERS_ENABLED) return`, described in its own comment as "a no-op when the feature is off, so an existing install is unaffected until the owner opts in". It is now unconditional: the server refuses to boot while any .env in the project root is group- or world-readable. A member's shell reading .env and printing JWT_SECRET was confirmed exploitable when this check was written, and a prerequisite that only holds when somebody remembers to set a variable is not a prerequisite. Nothing to remove on the environment side — the flag was never in .env.example or in the setup script. Not typechecked: node_modules is empty in this tree and installs are frozen, so tsgo could not run. All six files parse under `bun build --no-bundle`, and the changes are deletions of dead branches plus one removed early return. Formatted with prettier 3.9.6 via bunx rather than the pinned resolution, for the same reason; its one unrelated reformat was reverted by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,9 +18,6 @@ import { DATA_PATH, USER_DIRS, toShellUsername } from './data-path';
|
||||
// Everything here goes through `setpriv`. os-user.test.ts pins Bun's behaviour so that if it is ever
|
||||
// fixed, a test tells us we may simplify, rather than someone assuming it and being wrong.
|
||||
|
||||
/** Off unless explicitly enabled: this needs root, and a light install has no sudoers entry. */
|
||||
export const OS_USERS_ENABLED = process.env.OFFICER_OS_USERS === 'true' || process.env.OFFICER_OS_USERS === '1';
|
||||
|
||||
const MAX_USERNAME = 32;
|
||||
|
||||
/**
|
||||
@@ -484,23 +481,23 @@ export async function findReadableSecrets(projectDir: string): Promise<string[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuse to boot with OS users enabled while a secret in the project tree is readable by them.
|
||||
* Refuse to boot while a secret in the project tree is readable by other accounts on this machine.
|
||||
*
|
||||
* Same posture as `assertCapabilityTotality`, and for the same reason: this is a prerequisite that
|
||||
* silently not holding would make the whole feature theatre. Confirmed exploitable while testing — a
|
||||
* member's shell read `platform/.env` and printed `JWT_SECRET`, which is enough to mint an owner token and
|
||||
* bypass every capability check in the codebase.
|
||||
*
|
||||
* A no-op when the feature is off, so an existing install is unaffected until the owner opts in.
|
||||
* Unconditional. It was a no-op unless `OFFICER_OS_USERS` was set, which made the guarantee opt-in — and
|
||||
* a security prerequisite that only holds when someone remembers a flag is not a prerequisite.
|
||||
*/
|
||||
export async function assertSecretsClosed(projectDir: string): Promise<void> {
|
||||
if (!OS_USERS_ENABLED) return;
|
||||
const readable = await findReadableSecrets(projectDir);
|
||||
if (!readable.length) return;
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
'OFFICER_OS_USERS is enabled, but these files are readable by other accounts on this machine:',
|
||||
'These files are readable by other accounts on this machine:',
|
||||
'',
|
||||
...readable.map((p) => ` • ${p}`),
|
||||
'',
|
||||
|
||||
Reference in New Issue
Block a user