Skip to content

security(kpi): isolate strict KPI child-process environment #110

Description

@seonghobae

Problem

The KPI gate spawns scripts/check-kpi.mjs and scripts/evaluate-observability-alerts.mjs with an environment constructed as { ...process.env, ...(step.env ?? {}) }. That gives both scoring subprocesses every ambient workflow/model/provider/runner credential and configuration value even though their intended authority is local KPI evidence evaluation.

Fresh protected main remains c85d710804139c0697d7ef8fa47d02b1389e6d84. The same boundary is still present on currently moving PR #105 head 19b49cb6cc2e072eafa37fae2170d7d1f2ccdfa3, scripts/kpi-gate.mjs blob 8375211e393ad1ff8c67e848a92e015156cf63a3.

RCA

First failing boundary: spawnSync(step.command[0], ..., { env: { ...process.env, ...(step.env ?? {}) } }) in the parent KPI gate.

Immediate cause: ambient environment inheritance is explicitly copied into child authority.

Systemic cause: KPI subprocess inputs have no declared least-authority environment contract comparable to the repository's GitHub CLI/readiness subprocess hardening work.

Risk is unnecessary credential/configuration propagation rather than evidence that either current child intentionally reads a particular secret. A child dependency, preload/config hook, future code change or diagnostic path should not gain unrelated ambient authority by default.

Writer-safety defer

Do not start a parallel source branch while #105 is moving and owns scripts/kpi-gate.mjs plus its regression suite. #105's visible body is already stale relative to its live head, which is active-writer evidence. Revalidate after #105 settles or integrates, then repair on the surviving lineage rather than racing or duplicating that writer.

Acceptance criteria

  • Start from a freshly resolved protected main and the surviving KPI lineage after fix(kpi): bind strict provenance to exact log bytes #105 settles.
  • Add a realistic RED regression using a hostile synthetic parent environment containing GitHub/GITHUB tokens, NVIDIA/model credentials, Maintainer/Reviewer App material, Cloudflare/provider credentials, proxy values, HOME, NODE_OPTIONS, and unrelated NOEMA_* state.
  • Define the minimal child environment required by check-kpi.mjs and evaluate-observability-alerts.mjs; keep per-step NOEMA_KPI_REQUIRE_WINDOW_DAYS only where explicitly required.
  • Preserve executable resolution/runtime behavior without forwarding the complete parent environment.
  • Prove ambient NODE_OPTIONS/preload-style authority cannot silently cross this parent-child boundary unless an explicitly reviewed contract requires it.
  • Preserve strict provenance/snapshot/evidence-retention semantics from fix(kpi): bind strict provenance to exact log bytes #105.
  • Run focused RED -> GREEN, full exact-head release verification, current protected-base Security Scan, review/thread validation, and exact current-head coverage proof.
  • Do not weaken workflows, invent secrets/authority, or treat model/check/status evidence as formal approval.

Relationship to existing work

#107 narrows saleable-readiness subprocess authority and #109 narrows the hourly GitHub CLI subprocess environment. This issue is distinct: it covers the KPI gate's own child Node processes and must preserve #105's data-integrity work rather than overwriting it.

Related: #3, #27, #29, #104, #105, #107, #109

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions