Skip to content

CLI: parameterized workflows contaminate JSON stdout #456

Closed

Description

@Broccolito

Controlled audit follow-up to #424: parameterized workflows still prepend human parameter rows to machine-readable stdout.

Observed with clean source 971ebedf5c747a580195383e4f17f07e09f31b9c and a locally rebuilt normal CLI (SHA-256 5c1d629e1ef465f2747ea48926a3ca8f1dca78ac44459e69ce8eb61a448a5427). The profile, files, and loopback provider were synthetic; stdin was closed, and no --quiet workaround was used.

A controlled invocation of:

biorouter run --workflow biorouter-self-test.yaml \
  --params test_phases=folder_access --params test_depth=quick \
  --params workspace_dir=<synthetic-workspace> \
  --params parallel_tests=false --params cleanup_after=false \
  --output-format json </dev/null

exited 0 but stdout began with parameter rows such as cleanup_after: false and test_phases: folder_access, followed by the JSON object. Parsing the entire stdout failed with JSONDecodeError. The workflow-loading heading and parameter-summary heading were already on stderr. The actual original workflow made zero tool calls with this deterministic fixture, so its security phase is recorded as INCONCLUSIVE rather than passed. Separate direct and no-parameter derived-workflow controls produced actual structured authorization_required refusals and parseable JSON/NDJSON.

Cause: print_workflow_info in crates/biorouter-cli/src/workflows/print_workflow.rs calls the shared print_parameters_with_values, which prints rows to stdout. That helper is also used by human explanation output, where stdout is intentional.

Recommendations:

  • Send runtime workflow parameter rows to stderr with their existing headings, preserving the same readable information for terminal users.
  • Keep the intentional human --explain output intact.
  • Extend the real-binary, closed-stdin regression to a parameterized workflow in all three formats: whole JSON object, every NDJSON line, and ordinary text with its session banner.
  • Preserve the actual structured authorization refusal and synthetic canaries; this output correction must not change folder policy or grant access.

This is an automation/output correctness defect discovered during defensive security acceptance, not an observed authorization bypass. The original raw failure and later corrective results will remain separately attributable in the audit supplement.

Activity

  1. Broccolito commented on Oct 24, 2026

    @Broccolito
    CollaboratorAuthor

    Issue #456: Keep parameterized workflow machine stdout parseable

    Issue · Host PR #454 · merged main source

    Historical desktop/browser/CLI observations used f227 artifacts, accepted via the reviewed production-equivalence comparison; exact-main CI/build/smoke is attributed separately below.

    Problem. Runtime parameter diagnostics contaminated JSON stdout, breaking automation despite exit 0.

    Fix and UX. Workflow loading diagnostics now go to stderr. JSON remains one machine-readable value and stream-JSON remains parseable per line; useful terminal diagnostics and intentional human --explain output remain. Folder permissions do not change, and scripts need no --quiet workaround.

    Validation. The actual-binary regression passed one Rust test with direct/parameterized input × JSON/NDJSON/text: real provider-delta/output/canary assertions, closed stdin and parameter stderr. Final CLI05 independently passed six actual headless cases plus five normal PTY calls: 11 matched pairs total = three approved reads/eight refusals, nine executions exit 0, no orphan/unanswered pair and unchanged canaries/source. Machine output parses and parameter diagnostics are correctly attributed to stderr. Interactive once/Deny/session/list/revoke/reprompt remain usable. See current Rust CI and the local Supplementary Data S1 package for separate software/runtime attribution.

    Limits. Six format subcases are not six independent security attacks. The older 971 original 0-call JSON failure is preserved. A separate selected selftest now parsed but produced 0 tool calls; its security phase remains inconclusive, so no whole-workflow security PASS is inferred. This is an output/automation correction, not a new authorization capability.

    Recommendation. Keep runtime diagnostics on stderr and add machine-output regression coverage when workflow parameters change, preserving intentional human explanation output.

    Merged-main verification. PR454 is merged at 0580110; main Rust CI, frontend CI and related checks finished 20 successes/one skipped (17 push successes plus three landing/deployment checks; these are not security-test counts). Normal main build manifest SHA256: 38cc2979c7da104cb623975bac7f6922a085e834a1be5c18e961d7f26ea811fc; shipping receipt: e6e1d8957d8d0b22a9aae814011a1e028c5d279848ffabbf4c6d0c4edf3c0112. Actual main01 smoke separately recorded four pairs—three reads/one typed Deny—and three unchanged canaries; it does not rerun/recount the historical 81-pair cohort.

    Submission record. Manuscript v1.1 local Supplementary Data S1: Supplementary Data S1 - BioRouter Security Audit - 2026-10-24.zip; manifest SHA256 4f0c0c110ca0b07a29d5281870227f7b36c744a0f37ccb499b8ef1283dea116e. This is a user/submission package, not a claim of public upload. Public PR/source/CI links above provide the public implementation proof.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions