Skip to content

id(server_config): a blank or whitespace-padded key passes parse — a blank user key fails only mid-provisioning after earlier users are already created, and a padded key silently diverges from the account name it creates #1824

Closed

Description

@dezren39

id(server_config): a blank or whitespace-padded key passes parse — a blank user key only fails mid-provisioning, after earlier users are already created

Summary

ServerConfig::parse (pkgs/id/src/server_config/parse.rs) blank-checks every
human-facing string it accepts except the key itself:

  • group name: trim().is_empty() → ConfigError::Empty { field: "name" } (parse.rs, group pass)
  • alias: trimmed, blank → ConfigError::Empty { field: "alias" } (record_alias)
  • role: trimmed and lower-cased before matching (parse_role)

But claim() (the one place every user/group key passes through) records the
key verbatim — no trim, no empty check:

fn claim(names: &mut BTreeMap<String, Object>, key: &str, object: Object) -> Result<(), ConfigError> {
    if names.contains_key(key) {
        return Err(ConfigError::DuplicateKey(key.to_owned()));
    }
    names.insert(key.to_owned(), object);
    Ok(())
}

So key = "", key = " ", and key = " ann " are all accepted for users
and for groups, and every reference (creator, members[].user,
[create].users/groups) resolves against the verbatim string.

Why it matters

The user key is not just a local label: provisioning passes it as the
account's name — Provisioned::apply calls
directory.sign_up_keyed(&id, &user.key) (provision.rs), and
Directory::insert_account runs that name through clean_name
(directory.rs), which trims it and refuses an empty one
("name must not be empty"). Consequences, all verified this run (see below):

  1. A blank user key passes parse and fails only at provisioning, as a
    ProvisionError::User wrapping the directory's "name must not be empty" —
    not as a ConfigError naming the key. apply creates users in file
    order and returns on the first failure, so every user listed before the
    blank-key user has already been created
    when it fails. The module doc's
    promise — "ServerConfig::parse checks the whole file … so a bad file
    fails at startup" — is exactly what the blank check on names/aliases
    delivers and the key check misses.
  2. A whitespace-padded key silently diverges from the account it creates.
    Keys "ann" and " ann " are distinct keys to claim (no
    DuplicateKey), both provision successfully, and both accounts end up
    named ann (the directory trims; the config does not). Afterwards
    config.user_id(" ann ") resolves, while the directory knows the account
    only under the trimmed name.

Evidence (verified this run, main 528886e)

Laptop probe in the real crate (worktree of origin/main at
~/git/fleet/probe-2026-10-28-laneA-configkey, integration test
pkgs/id/tests/probe_configkey.rs calling the public
ServerConfig::parse / Provisioned::apply; parse.rs sha256 on the
laptop worktree identical to the sandbox clone,
14673ade04923b574ea2adaedeebda03cf967e0d403bed28e4caa6cec450210d):

  • key = "" user → parse Ok; key = " " user → parse Ok;
    blank group key → parse Ok
  • controls: blank group name → Err(Empty { field: "name" });
    blank alias → Err(Empty { field: "alias" })
  • config [ann (good), "" (blank)] → apply fails at the blank user with
    the directory's empty-name refusal, and the good user's account already
    exists in the directory afterwards
  • config with keys "ann" and " ann " (different ids) → parse Ok, apply
    Ok, and the directory holds two accounts both named ann

(Full probe output is attached in the filing run's bookkeeping:
hidden_files/probe-2026-10-28-laneA-configkey/ in the fleet goal workspace.)

Acceptance criteria

  • ServerConfig::parse refuses a user or group key that is empty or
    whitespace-only, with a ConfigError that names the offending entry the
    way Empty names a blank name/alias (a dedicated variant is fine).
  • The rule for surrounding whitespace is pinned one way or the other by
    tests: either keys are trimmed before claiming (so " ann " is ann,
    and a repeat is DuplicateKey), or padded keys are refused at parse.
    Silent divergence between the config key and the provisioned account
    name must be impossible.
  • References (creator, members, [create]) follow the same rule as the
    declarations they name.
  • Existing config parse/provision tests keep passing, including the
    blank name/alias refusals and the duplicate-key refusals.
  • New tests cover: blank user key, blank group key, whitespace-only key,
    and the pinned padded-key behaviour.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · state: filed

    • Agent: new-issue-creator job (new-issue-creator-code-a)
    • Session / ID: new-issue-creator-code-a run 2026-10-28 15:44 CDT (job_id new-issue-creator-code-a)
    • Found via: filed this run
    • Task: Filed this issue: server_config parse never blank-checks user/group keys — a blank user key fails only mid-provisioning (after earlier users are created) and a whitespace-padded key silently diverges from the trimmed account name it creates. Verified this run with a real-crate probe on the laptop at origin/main 528886e.
    • Expectation: effort/xs–s; done when parse refuses blank/whitespace-only keys naming the entry, the padded-key rule is pinned by tests, and existing parse/provision tests pass.
    • Links: queue packet enqueued this run (repo-health-code-pilot)
  4. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 9 remaining items

  6. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added
    queue/needs-reviewQueue task finished and is awaiting review
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  10. added a commit that references this issue on Nov 1, 2026
  11. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions