Background
POST /orgs/{org}/servers returns the new machine's generated sudo password in data.meta.sudo_password. Forge returns it only once on the create response — there's no GET endpoint to fetch it later.
Today ServersResource::create() reads only the data resource object and hydrates Data\Server, so meta.sudo_password is silently dropped. That's safe (it's never persisted or committed — ServerFixture also redacts it), but it means a caller who needs the sudo password can't get it through the SDK, and it's then unrecoverable.
Not a security issue
This is a feature gap, not a leak: nothing sensitive is exposed or stored. Filed as an enhancement.
Proposed shape
Return a small wrapper from create() so the one-time secret is available without polluting the persisted DTO, e.g.:
final readonly class CreatedServer {
public function __construct(
public Server $server,
public ?string $sudoPassword, // null on providers that don't return one
) {}
}
ServersResource::create(): CreatedServer. Keep Server itself free of the password so it never lands in logs/serialization by accident.
Acceptance criteria
Context
Surfaced during the v0.1.0 release review (#10) and the security audit (#27). Not a v0.1.0 blocker.
Background
POST /orgs/{org}/serversreturns the new machine's generated sudo password indata.meta.sudo_password. Forge returns it only once on the create response — there's no GET endpoint to fetch it later.Today
ServersResource::create()reads only thedataresource object and hydratesData\Server, someta.sudo_passwordis silently dropped. That's safe (it's never persisted or committed —ServerFixturealso redacts it), but it means a caller who needs the sudo password can't get it through the SDK, and it's then unrecoverable.Not a security issue
This is a feature gap, not a leak: nothing sensitive is exposed or stored. Filed as an enhancement.
Proposed shape
Return a small wrapper from
create()so the one-time secret is available without polluting the persisted DTO, e.g.:ServersResource::create(): CreatedServer. KeepServeritself free of the password so it never lands in logs/serialization by accident.Acceptance criteria
create()exposes the sudo password without storing it onServerdocs/FINDINGS.mdnote that it's create-only / unrecoverablesudo_passwordcreate()'s return type (it is) — schedule for a minor/major accordinglyContext
Surfaced during the v0.1.0 release review (#10) and the security audit (#27). Not a v0.1.0 blocker.