Skip to content

run.secrets can still bind a few steering names: lowercase Google twins, the billing project, and pi's own agent directory #511

Closed

Description

@edgehero

Why this is needed

run.secrets refuses the provider steering variables pi and its provider SDKs read, derived from their sources (worker/src/provider-steering.mjs, re-derived for pi 0.99.1 in #509). A scan of about 180 environment names read across the 0.99.1 SDKs found a few that steer a request or a credential and are still bindable. All of them were equally bindable before #509:

  • Lowercase twins of reserved Google names. google_application_credentials is read by google-auth-library (build/src/auth/googleauth.js:316) and was measured planting a credential file the job then uses. gcloud_project and google_cloud_project are the same kind of twin for the project variables. The derivation compares names exactly, so the uppercase forms are refused and the lowercase ones are not.
  • GOOGLE_CLOUD_QUOTA_PROJECT chooses which project is billed (googleauth.js:286).
  • PI_CODING_AGENT_DIR: pi's own environment reads are outside the derivation entirely, so a trigger can point the job's pi at another agent directory.

Who is affected: an operator whose triggers file is edited by someone they trust less than the deployment (the file is reviewed, so this is defence in depth, not an open door).

What to build

  1. Make the derivation case insensitive where the reading code is (google-auth reads both spellings), or add each lowercase twin the scan finds, pinned by the scan so a new twin fails the test.
  2. Add GOOGLE_CLOUD_QUOTA_PROJECT to the reserved set through the scan, not by hand.
  3. Extend the scan to pi-coding-agent's and pi-ai's own process.env reads, subtracting the variables the runner sets itself, and reserve the ones that change where pi reads its configuration or credentials (PI_CODING_AGENT_DIR and any siblings).

What not to do

  • Do not hand-type the new names: the list must stay derived from the sources, or it drifts at the next pi bump.
  • Do not refuse names no pinned source reads.

How to test it

  • worker/test/provider-steering.test.mjs: the scan finds each name above against the pinned 0.99.1 sources, and a triggers file binding any of them is refused at load; a mutation that drops the lowercase handling turns it red.
  • By hand: a triggers file with run.secrets naming google_application_credentials is refused by pi-dispatch doctor and by the worker at boot.

Acceptance

  • Every name above is refused in run.secrets, by derivation.
  • The scan covers pi's own environment reads.

Depends on

#509 (the 0.99.1 sources the scan reads).

Activity

  1. edgehero commented on Oct 2, 2026

    @edgehero
    OwnerAuthor

    Fixed in #513 (9449e69).

    • run.secrets now refuses every provider steering name the pinned 0.99.1 sources read. The list is derived, never hand-typed, and a two-way test checks it against the sources: 132 names (64 added, none removed), plus four hand-kept residuals.
    • What the scan reads:
      • both pi-ai copies: the hoisted one, and the runner's, nested under pi-coding-agent (google-auth-library 10.9.1 and 10.6.2);
      • one more dependency hop, through declared dependencies (google-auth-library, @smithy/core, the AWS chain, debug, ws);
      • every PI_* name pi itself reads, minus the names the worker writes and the runner sets (PI_OFFLINE, PI_TELEMETRY).
    • How it stays complete: comments are stripped first. Every environment access in every scanned file must then either name a variable or be counted, per file, in a pinned list. Helpers that read process.env[param] are derived from the code, so a new reading site or a new caller fails the test instead of passing silently. Forms with no env token at all (for example process["e"+"nv"]) are a stated limit; none occurs at the pin.
    • The names from this issue: the lowercase twins (google_application_credentials, gcloud_project, google_cloud_project), GOOGLE_CLOUD_QUOTA_PROJECT and PI_CODING_AGENT_DIR are all refused at load. Matching stays exact, so names nothing reads (openai_base_url, anthropic_api_key) stay bindable.
    • Found in review: reserved names were checked only when the triggers file loads. A job already queued, a stored cron template or an older receiver could still carry one. The processor now refuses such a job before the resolver runs and before any spend (secret-name-reserved).
    • Release notes must say: a triggers file binding a newly reserved name stops loading. Some of those are ordinary names: DEBUG, K_CONFIGURATION, AWS_EXECUTION_ENV, PI_TUI_DEBUG. Put them on PI_FORWARD_ENV instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions