From e8298485c3a4215bbf139c58db20be7abcc90fe1 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Sat, 24 Oct 2026 12:37:39 +0200 Subject: [PATCH] bugfix-682-protect-credential-health-requests-from-process-argument-disclosure: keep credential health secrets out of process arguments (#682) --- .../workflows/copilot_credential_health.yml | 36 +++-- build/cli/index.js | 2 +- .../security/credentials.mdx | 14 ++ setup/workflows/copilot_credential_health.yml | 36 +++-- specs/CATALOG.md | 6 +- specs/catalog.json | 5 +- ...up-configuration-credentials-and-doctor.md | 75 +++++++++- .../setup_permission_actions_probe.ts | 2 +- .../credential_health_transport.test.ts | 139 ++++++++++++++++++ 9 files changed, 289 insertions(+), 26 deletions(-) create mode 100644 src/tooling/__tests__/credential_health_transport.test.ts diff --git a/.github/workflows/copilot_credential_health.yml b/.github/workflows/copilot_credential_health.yml index e87c82a3b..2ccf3f70f 100644 --- a/.github/workflows/copilot_credential_health.yml +++ b/.github/workflows/copilot_credential_health.yml @@ -59,10 +59,12 @@ jobs: env: PAT: ${{ secrets.PAT }} run: | + set +x set -euo pipefail [[ -n "${PAT:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $PAT" -H 'Accept: application/vnd.github+json' https://api.github.com/user >/dev/null - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $PAT" -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/${GITHUB_REPOSITORY}" >/dev/null + [[ "$PAT" != *$'\r'* && "$PAT" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$PAT" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'Accept: application/vnd.github+json' https://api.github.com/user >/dev/null + printf 'Authorization: Bearer %s\n' "$PAT" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'Accept: application/vnd.github+json' "https://api.github.com/repos/${GITHUB_REPOSITORY}" >/dev/null verify-openai: if: ${{ inputs.check_openai }} @@ -75,9 +77,11 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENAI_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENAI_API_KEY" https://api.openai.com/v1/models >/dev/null + [[ "$OPENAI_API_KEY" != *$'\r'* && "$OPENAI_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENAI_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://api.openai.com/v1/models >/dev/null verify-anthropic: if: ${{ inputs.check_anthropic }} @@ -90,9 +94,11 @@ jobs: env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${ANTHROPIC_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "x-api-key: $ANTHROPIC_API_KEY" -H 'anthropic-version: 2023-06-01' https://api.anthropic.com/v1/models >/dev/null + [[ "$ANTHROPIC_API_KEY" != *$'\r'* && "$ANTHROPIC_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'x-api-key: %s\n' "$ANTHROPIC_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'anthropic-version: 2023-06-01' https://api.anthropic.com/v1/models >/dev/null verify-google: if: ${{ inputs.check_google }} @@ -105,9 +111,11 @@ jobs: env: GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${GOOGLE_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 "https://generativelanguage.googleapis.com/v1beta/models?key=$GOOGLE_API_KEY" >/dev/null + [[ "$GOOGLE_API_KEY" != *$'\r'* && "$GOOGLE_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'x-goog-api-key: %s\n' "$GOOGLE_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://generativelanguage.googleapis.com/v1beta/models >/dev/null verify-openrouter: if: ${{ inputs.check_openrouter }} @@ -120,9 +128,11 @@ jobs: env: OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENROUTER_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/models >/dev/null + [[ "$OPENROUTER_API_KEY" != *$'\r'* && "$OPENROUTER_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENROUTER_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://openrouter.ai/api/v1/models >/dev/null verify-cursor: if: ${{ inputs.check_cursor }} @@ -135,9 +145,13 @@ jobs: env: CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${CURSOR_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -u "$CURSOR_API_KEY:" 'https://api.cursor.com/analytics/ai-code/changes?startDate=30d&page=1&pageSize=1' >/dev/null + [[ "$CURSOR_API_KEY" != *$'\r'* && "$CURSOR_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + basic_auth="$(printf '%s:' "$CURSOR_API_KEY" | base64 | tr -d '\r\n')" + printf 'Authorization: Basic %s\n' "$basic_auth" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- 'https://api.cursor.com/analytics/ai-code/changes?startDate=30d&page=1&pageSize=1' >/dev/null + unset basic_auth verify-opencode: if: ${{ inputs.check_opencode }} @@ -150,9 +164,11 @@ jobs: env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENCODE_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENCODE_API_KEY" https://opencode.ai/zen/v1/models >/dev/null + [[ "$OPENCODE_API_KEY" != *$'\r'* && "$OPENCODE_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENCODE_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://opencode.ai/zen/v1/models >/dev/null verify-codex-api-key: if: ${{ inputs.check_codex_api_key }} @@ -165,6 +181,8 @@ jobs: env: CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${CODEX_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $CODEX_API_KEY" https://api.openai.com/v1/models >/dev/null + [[ "$CODEX_API_KEY" != *$'\r'* && "$CODEX_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$CODEX_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://api.openai.com/v1/models >/dev/null diff --git a/build/cli/index.js b/build/cli/index.js index 31e804bd7..0960966fa 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -100913,7 +100913,7 @@ const MAX_WORKFLOW_PAGES = 5; const MAX_WORKFLOW_FILE_CHECKS = 64; // Hash of the packaged credential-health workflow: every job is guarded by a // false-by-default input. A different remote file is never dispatched directly. -const TRUSTED_HEALTH_WORKFLOW_SHA256 = '7fa36bf72d144df6fe2ccc45b805ad442187aa6979dfda71446a54f607b18d61'; +const TRUSTED_HEALTH_WORKFLOW_SHA256 = '5f093b2c03bef389ccea68ef43bec3577cb1bfddc038da1884a146088f39a4ec'; /** Dispatches a disabled-job branch override of a workflow already registered on default. */ async function probeActions(context) { const preparationHttp = context.operatorHttp ?? context.http; diff --git a/docs/security-operations/security/credentials.mdx b/docs/security-operations/security/credentials.mdx index 790b2694e..75086ad9e 100644 --- a/docs/security-operations/security/credentials.mdx +++ b/docs/security-operations/security/credentials.mdx @@ -45,3 +45,17 @@ Temporary permission probes are separate disposable resources with collision checks, private names and cleanup receipts; their final review warns that GitHub Secret writes are upserts without an atomic create-only guarantee. Pending cleanup stops installation. Revoke the temporary operator PAT yourself when done. + +The opt-in credential health workflow sends authentication headers to curl over +stdin. Raw keys and Basic authentication values stay out of process arguments +and request URLs. Only the selected check receives its named Secret. Empty or +multiline values fail before a request, and HTTPS requests reject redirects so +custom provider headers cannot follow them to another origin. Response bodies +are discarded; existing job names, flags and 20-second request limits remain. + +If a check reports `Credential contains invalid line breaks.`, replace that +named Secret with its single-line value and explicitly rerun the check. If its +canonical endpoint starts redirecting, review the provider endpoint before +updating the workflow. Installations receive this correction by updating their +managed `copilot_credential_health.yml` through the reviewed setup upgrade; a +merge in Copilot alone does not replace existing consumer workflows. diff --git a/setup/workflows/copilot_credential_health.yml b/setup/workflows/copilot_credential_health.yml index e87c82a3b..2ccf3f70f 100644 --- a/setup/workflows/copilot_credential_health.yml +++ b/setup/workflows/copilot_credential_health.yml @@ -59,10 +59,12 @@ jobs: env: PAT: ${{ secrets.PAT }} run: | + set +x set -euo pipefail [[ -n "${PAT:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $PAT" -H 'Accept: application/vnd.github+json' https://api.github.com/user >/dev/null - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $PAT" -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/${GITHUB_REPOSITORY}" >/dev/null + [[ "$PAT" != *$'\r'* && "$PAT" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$PAT" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'Accept: application/vnd.github+json' https://api.github.com/user >/dev/null + printf 'Authorization: Bearer %s\n' "$PAT" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'Accept: application/vnd.github+json' "https://api.github.com/repos/${GITHUB_REPOSITORY}" >/dev/null verify-openai: if: ${{ inputs.check_openai }} @@ -75,9 +77,11 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENAI_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENAI_API_KEY" https://api.openai.com/v1/models >/dev/null + [[ "$OPENAI_API_KEY" != *$'\r'* && "$OPENAI_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENAI_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://api.openai.com/v1/models >/dev/null verify-anthropic: if: ${{ inputs.check_anthropic }} @@ -90,9 +94,11 @@ jobs: env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${ANTHROPIC_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "x-api-key: $ANTHROPIC_API_KEY" -H 'anthropic-version: 2023-06-01' https://api.anthropic.com/v1/models >/dev/null + [[ "$ANTHROPIC_API_KEY" != *$'\r'* && "$ANTHROPIC_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'x-api-key: %s\n' "$ANTHROPIC_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- --header 'anthropic-version: 2023-06-01' https://api.anthropic.com/v1/models >/dev/null verify-google: if: ${{ inputs.check_google }} @@ -105,9 +111,11 @@ jobs: env: GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${GOOGLE_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 "https://generativelanguage.googleapis.com/v1beta/models?key=$GOOGLE_API_KEY" >/dev/null + [[ "$GOOGLE_API_KEY" != *$'\r'* && "$GOOGLE_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'x-goog-api-key: %s\n' "$GOOGLE_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://generativelanguage.googleapis.com/v1beta/models >/dev/null verify-openrouter: if: ${{ inputs.check_openrouter }} @@ -120,9 +128,11 @@ jobs: env: OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENROUTER_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/models >/dev/null + [[ "$OPENROUTER_API_KEY" != *$'\r'* && "$OPENROUTER_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENROUTER_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://openrouter.ai/api/v1/models >/dev/null verify-cursor: if: ${{ inputs.check_cursor }} @@ -135,9 +145,13 @@ jobs: env: CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${CURSOR_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -u "$CURSOR_API_KEY:" 'https://api.cursor.com/analytics/ai-code/changes?startDate=30d&page=1&pageSize=1' >/dev/null + [[ "$CURSOR_API_KEY" != *$'\r'* && "$CURSOR_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + basic_auth="$(printf '%s:' "$CURSOR_API_KEY" | base64 | tr -d '\r\n')" + printf 'Authorization: Basic %s\n' "$basic_auth" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- 'https://api.cursor.com/analytics/ai-code/changes?startDate=30d&page=1&pageSize=1' >/dev/null + unset basic_auth verify-opencode: if: ${{ inputs.check_opencode }} @@ -150,9 +164,11 @@ jobs: env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${OPENCODE_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $OPENCODE_API_KEY" https://opencode.ai/zen/v1/models >/dev/null + [[ "$OPENCODE_API_KEY" != *$'\r'* && "$OPENCODE_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$OPENCODE_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://opencode.ai/zen/v1/models >/dev/null verify-codex-api-key: if: ${{ inputs.check_codex_api_key }} @@ -165,6 +181,8 @@ jobs: env: CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} run: | + set +x set -euo pipefail [[ -n "${CODEX_API_KEY:-}" ]] || { echo 'Required credential is missing.' >&2; exit 1; } - curl --fail --silent --show-error --location --max-time 20 -H "Authorization: Bearer $CODEX_API_KEY" https://api.openai.com/v1/models >/dev/null + [[ "$CODEX_API_KEY" != *$'\r'* && "$CODEX_API_KEY" != *$'\n'* ]] || { echo 'Credential contains invalid line breaks.' >&2; exit 1; } + printf 'Authorization: Bearer %s\n' "$CODEX_API_KEY" | curl -q --fail --silent --show-error --location --max-redirs 0 --proto '=https' --proto-redir '=https' --max-time 20 --header @- https://api.openai.com/v1/models >/dev/null diff --git a/specs/CATALOG.md b/specs/CATALOG.md index 736bc3ac6..a143eb019 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -16,7 +16,7 @@ debt or convert unknown historic intent into a design decision. | `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 70 paths · 2026-10-21 | | `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 105 paths · 2026-10-21 | | `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 72 paths · 2026-10-13 | -| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 6 companion | 331 paths · 2026-10-20 | +| `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 6 companion | 332 paths · 2026-10-24 | | `local-web-setup-assistant` | Proposed | Offer a packaged, loopback-only Svelte setup interface over the existing engine with four-language guidance, English CLI help, basic/custom first-run paths, editable question and plan review, source-labelled GitHub facts, assisted CI and Project evidence, role-separated PATs, and truthful itemized outcomes; inspect existing configuration and apply independent, scope-aware runtime adjustments with permission, drift and read-back verification | [Local web setup assistant](./local-web-setup-assistant.md) + 2 companion | 247 paths · 2026-10-21 | | `guided-bot-pat-onboarding` | Proposed | Guide creation of the persistent workflow PAT, verify bot identity and grants, recover explicitly from wrong-account input within the session, and install the approved Actions Secret | [Guided bot PAT onboarding](./guided-bot-pat-onboarding.md) | 39 paths · 2026-10-07 | | `temporary-setup-operator-authorization` | Proposed | Guide creation of the one-run operator PAT through GitHub's official form, verify final setup access, and report user-owned deletion accurately | [Assisted setup PAT creation](./temporary-setup-operator-authorization.md) | 36 paths · 2026-10-18 | @@ -103,12 +103,12 @@ debt or convert unknown historic intent into a design decision. ### `setup-and-doctor` — Setup, configuration, credentials, and doctor - Owner: Copilot maintainers -- Last verified: 2026-10-20 +- Last verified: 2026-10-24 - Specifications: [`specs/setup-configuration-credentials-and-doctor.md`](./setup-configuration-credentials-and-doctor.md) · [`specs/setup-doctor-architecture-hardening.md`](./setup-doctor-architecture-hardening.md) · [`specs/setup-pat-permission-guidance-and-verification.md`](./setup-pat-permission-guidance-and-verification.md) · [`specs/github-runtime-doctor-command.md`](./github-runtime-doctor-command.md) · [`specs/organization-configuration-reuse-and-batch-setup.md`](./organization-configuration-reuse-and-batch-setup.md) · [`specs/setup-runner-profiles-and-installation-parity.md`](./setup-runner-profiles-and-installation-parity.md) · [`specs/copilot-comment-workflow-coordinator.md`](./copilot-comment-workflow-coordinator.md) - Workflows: [`setup/workflows/agent-cli-provisioning.yml`](../setup/workflows/agent-cli-provisioning.yml) · [`setup/workflows/copilot_credential_health.yml`](../setup/workflows/copilot_credential_health.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) · [`.github/workflows/agent-cli-provisioning.yml`](../.github/workflows/agent-cli-provisioning.yml) · [`.github/workflows/copilot_credential_health.yml`](../.github/workflows/copilot_credential_health.yml) · [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_branch_sync.yml`](../.github/workflows/copilot_branch_sync.yml) · [`setup/workflows/copilot_commit.yml`](../setup/workflows/copilot_commit.yml) · [`setup/workflows/copilot_branch_sync.yml`](../setup/workflows/copilot_branch_sync.yml) · [`setup/workflows/copilot_issue_comment.yml`](../setup/workflows/copilot_issue_comment.yml) · [`setup/workflows/copilot_pull_request_comment.yml`](../setup/workflows/copilot_pull_request_comment.yml) - Entrypoints: [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) · [`src/application/usecases/issue_comment_use_case.ts`](../src/application/usecases/issue_comment_use_case.ts) · [`src/application/usecases/pull_request_review_comment_use_case.ts`](../src/application/usecases/pull_request_review_comment_use_case.ts) - Core code: [`src/domain/setup.ts`](../src/domain/setup.ts) · [`src/domain/setup_questionnaire.ts`](../src/domain/setup_questionnaire.ts) · [`src/domain/setup_token_permissions.ts`](../src/domain/setup_token_permissions.ts) · [`src/application/ports/setup_terminal_ports.ts`](../src/application/ports/setup_terminal_ports.ts) · [`src/application/ports/setup_wizard_ports.ts`](../src/application/ports/setup_wizard_ports.ts) · [`src/application/ports/branch_tag_ports.ts`](../src/application/ports/branch_tag_ports.ts) · [`src/application/policies/setup_issue_workflow_policy.ts`](../src/application/policies/setup_issue_workflow_policy.ts) · [`src/data/repository/release/repository_version_tags_query_repository.ts`](../src/data/repository/release/repository_version_tags_query_repository.ts) · [`src/application/ports/setup_token_permission_ports.ts`](../src/application/ports/setup_token_permission_ports.ts) · [`src/application/policies/setup_token_permission_evidence_policy.ts`](../src/application/policies/setup_token_permission_evidence_policy.ts) · [`src/application/policies/setup_token_permission_policy.ts`](../src/application/policies/setup_token_permission_policy.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/application/policies/setup_configuration_plan.ts`](../src/application/policies/setup_configuration_plan.ts) · [`src/application/policies/merge_setup_overrides_policy.ts`](../src/application/policies/merge_setup_overrides_policy.ts) · [`src/application/policies/setup_configuration_storage_policy.ts`](../src/application/policies/setup_configuration_storage_policy.ts) · [`src/application/policies/setup_doctor_message_catalog.ts`](../src/application/policies/setup_doctor_message_catalog.ts) · [`src/application/policies/setup_doctor_report_policy.ts`](../src/application/policies/setup_doctor_report_policy.ts) · [`src/application/policies/setup_journey_policy.ts`](../src/application/policies/setup_journey_policy.ts) · [`src/application/policies/setup_permission_summary_policy.ts`](../src/application/policies/setup_permission_summary_policy.ts) · [`src/application/usecases/setup/setup_wizard_use_case.ts`](../src/application/usecases/setup/setup_wizard_use_case.ts) · [`src/application/usecases/setup/setup_journey_use_case.ts`](../src/application/usecases/setup/setup_journey_use_case.ts) · [`src/application/usecases/setup/setup_session_coordinator.ts`](../src/application/usecases/setup/setup_session_coordinator.ts) · [`src/application/usecases/setup/setup_questionnaire_controller.ts`](../src/application/usecases/setup/setup_questionnaire_controller.ts) · [`src/application/usecases/setup/setup_credentials_use_case.ts`](../src/application/usecases/setup/setup_credentials_use_case.ts) · [`src/application/usecases/setup/setup_token_permissions_use_case.ts`](../src/application/usecases/setup/setup_token_permissions_use_case.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/usecases/setup/merge_queue_readiness_use_case.ts`](../src/application/usecases/setup/merge_queue_readiness_use_case.ts) · [`src/application/usecases/actions/initial_setup_workflow.ts`](../src/application/usecases/actions/initial_setup_workflow.ts) · [`src/application/usecases/actions/setup_resource_provisioning.ts`](../src/application/usecases/actions/setup_resource_provisioning.ts) · [`src/application/ports/message_catalog_ports.ts`](../src/application/ports/message_catalog_ports.ts) · [`src/application/usecases/localization/resolve_message_catalog_use_case.ts`](../src/application/usecases/localization/resolve_message_catalog_use_case.ts) · [`src/application/policies/setup_configuration_validation.ts`](../src/application/policies/setup_configuration_validation.ts) · [`src/infrastructure/setup_workspace_adapter.ts`](../src/infrastructure/setup_workspace_adapter.ts) · [`src/data/repository/repository_variables_repository.ts`](../src/data/repository/repository_variables_repository.ts) · [`src/infrastructure/github/ports/github_repository_variables_protocol.ts`](../src/infrastructure/github/ports/github_repository_variables_protocol.ts) · [`src/infrastructure/setup_remote_credential_health_adapter.ts`](../src/infrastructure/setup_remote_credential_health_adapter.ts) · [`src/infrastructure/setup_credential_validation_adapter.ts`](../src/infrastructure/setup_credential_validation_adapter.ts) · [`src/infrastructure/setup_token_permission_query_adapter.ts`](../src/infrastructure/setup_token_permission_query_adapter.ts) · [`src/infrastructure/setup_permission_actions_probe.ts`](../src/infrastructure/setup_permission_actions_probe.ts) · [`src/infrastructure/setup_permission_issue_probe.ts`](../src/infrastructure/setup_permission_issue_probe.ts) · [`src/infrastructure/setup_permission_issue_cleanup.ts`](../src/infrastructure/setup_permission_issue_cleanup.ts) · [`src/infrastructure/setup_permission_probe_journal.ts`](../src/infrastructure/setup_permission_probe_journal.ts) · [`src/infrastructure/setup_permission_probe_transaction.ts`](../src/infrastructure/setup_permission_probe_transaction.ts) · [`src/infrastructure/setup_permission_resource_probes.ts`](../src/infrastructure/setup_permission_resource_probes.ts) · [`src/infrastructure/setup_projects_probe_page_policy.ts`](../src/infrastructure/setup_projects_probe_page_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/cli/setup_question_renderer.ts`](../src/cli/setup_question_renderer.ts) · [`src/cli/setup_journey_presenter.ts`](../src/cli/setup_journey_presenter.ts) · [`src/cli/setup_plan_presenter.ts`](../src/cli/setup_plan_presenter.ts) · [`src/cli/setup_doctor_presenter.ts`](../src/cli/setup_doctor_presenter.ts) · [`src/cli/setup_prompt_rendering.ts`](../src/cli/setup_prompt_rendering.ts) · [`src/cli/setup_credential_prompt_adapter.ts`](../src/cli/setup_credential_prompt_adapter.ts) · [`src/cli/setup_credential_collection.ts`](../src/cli/setup_credential_collection.ts) · [`src/cli/commands/setup_execution.ts`](../src/cli/commands/setup_execution.ts) · [`src/cli/setup_token_permission_presenter.ts`](../src/cli/setup_token_permission_presenter.ts) · [`src/infrastructure/composition/setup_credentials_composition_root.ts`](../src/infrastructure/composition/setup_credentials_composition_root.ts) · [`src/infrastructure/composition/setup_token_permissions_composition_root.ts`](../src/infrastructure/composition/setup_token_permissions_composition_root.ts) · [`src/infrastructure/composition/setup_doctor_composition_root.ts`](../src/infrastructure/composition/setup_doctor_composition_root.ts) · [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/documentation_pat_exception_policy.cjs`](../scripts/documentation_pat_exception_policy.cjs) · [`scripts/validate-documentation-contract.cjs`](../scripts/validate-documentation-contract.cjs) · [`src/infrastructure/http_deadline.ts`](../src/infrastructure/http_deadline.ts) · [`src/infrastructure/setup_permission_denial.ts`](../src/infrastructure/setup_permission_denial.ts) · [`src/infrastructure/setup_permission_read_probe.ts`](../src/infrastructure/setup_permission_read_probe.ts) · [`src/infrastructure/setup_permission_read_evidence.ts`](../src/infrastructure/setup_permission_read_evidence.ts) · [`src/infrastructure/setup_permission_read_shape.ts`](../src/infrastructure/setup_permission_read_shape.ts) · [`src/infrastructure/setup_permission_projects_read.ts`](../src/infrastructure/setup_permission_projects_read.ts) · [`src/infrastructure/setup_permission_projects_access.ts`](../src/infrastructure/setup_permission_projects_access.ts) · [`src/infrastructure/setup_permission_workflow_cleanup.ts`](../src/infrastructure/setup_permission_workflow_cleanup.ts) · [`src/infrastructure/setup_permission_project_cleanup.ts`](../src/infrastructure/setup_permission_project_cleanup.ts) · [`src/infrastructure/setup_permission_probe_http.ts`](../src/infrastructure/setup_permission_probe_http.ts) · [`src/infrastructure/setup_permission_pull_request_probe.ts`](../src/infrastructure/setup_permission_pull_request_probe.ts) · [`src/infrastructure/git_authentication_environment.ts`](../src/infrastructure/git_authentication_environment.ts) · [`src/actions/github_action_input.ts`](../src/actions/github_action_input.ts) · [`src/infrastructure/setup_github_identity_query_adapter.ts`](../src/infrastructure/setup_github_identity_query_adapter.ts) · [`src/infrastructure/github_secret_encryption.ts`](../src/infrastructure/github_secret_encryption.ts) · [`src/infrastructure/setup_permission_organization_owner.ts`](../src/infrastructure/setup_permission_organization_owner.ts) · [`src/data/repository/github/github_actions_resource_inspector.ts`](../src/data/repository/github/github_actions_resource_inspector.ts) · [`src/data/repository/github/github_actions_resource_commands.ts`](../src/data/repository/github/github_actions_resource_commands.ts) · [`src/data/repository/github/github_actions_resource_collection.ts`](../src/data/repository/github/github_actions_resource_collection.ts) · [`src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts`](../src/application/usecases/setup/resolve_setup_workflow_pat_conflict_use_case.ts) · [`src/cli/setup_workflow_pat_storage_copy.ts`](../src/cli/setup_workflow_pat_storage_copy.ts) · [`src/cli/commands/setup_policy.ts`](../src/cli/commands/setup_policy.ts) · [`src/application/usecases/execution/execution_issue_number_policy.ts`](../src/application/usecases/execution/execution_issue_number_policy.ts) · [`src/application/usecases/execution/setup_execution_workflow.ts`](../src/application/usecases/execution/setup_execution_workflow.ts) · [`src/domain/github_runtime_doctor.ts`](../src/domain/github_runtime_doctor.ts) · [`src/domain/copilot_command.ts`](../src/domain/copilot_command.ts) · [`src/application/usecases/comment_automation_command_workflow.ts`](../src/application/usecases/comment_automation_command_workflow.ts) · [`src/application/policies/copilot_interaction_policy.ts`](../src/application/policies/copilot_interaction_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/ports/github_runtime_doctor_ports.ts`](../src/application/ports/github_runtime_doctor_ports.ts) · [`src/application/policies/github_runtime_doctor_policy.ts`](../src/application/policies/github_runtime_doctor_policy.ts) · [`src/application/policies/doctor_workflow_condition_policy.ts`](../src/application/policies/doctor_workflow_condition_policy.ts) · [`src/application/policies/github_runtime_doctor_presentation_policy.ts`](../src/application/policies/github_runtime_doctor_presentation_policy.ts) · [`src/application/usecases/read_copilot_doctor_workflow.ts`](../src/application/usecases/read_copilot_doctor_workflow.ts) · [`src/data/repository/github/runtime_doctor_repository.ts`](../src/data/repository/github/runtime_doctor_repository.ts) · [`src/infrastructure/composition/github_runtime_doctor_composition_root.ts`](../src/infrastructure/composition/github_runtime_doctor_composition_root.ts) · [`src/application/policies/agent_activity_policy.ts`](../src/application/policies/agent_activity_policy.ts) · [`src/application/policies/doctor_workflow_matrix_policy.ts`](../src/application/policies/doctor_workflow_matrix_policy.ts) · [`src/application/contracts/setup_batch_view.ts`](../src/application/contracts/setup_batch_view.ts) · [`src/application/policies/issue_resource_input_policy.ts`](../src/application/policies/issue_resource_input_policy.ts) · [`src/application/policies/setup_batch_access_policy.ts`](../src/application/policies/setup_batch_access_policy.ts) · [`src/application/policies/setup_batch_policy.ts`](../src/application/policies/setup_batch_policy.ts) · [`src/application/policies/setup_batch_retry_policy.ts`](../src/application/policies/setup_batch_retry_policy.ts) · [`src/application/policies/setup_batch_selection_policy.ts`](../src/application/policies/setup_batch_selection_policy.ts) · [`src/application/policies/setup_batch_view_policy.ts`](../src/application/policies/setup_batch_view_policy.ts) · [`src/application/policies/setup_credential_binding_policy.ts`](../src/application/policies/setup_credential_binding_policy.ts) · [`src/application/policies/setup_organization_import_policy.ts`](../src/application/policies/setup_organization_import_policy.ts) · [`src/application/policies/setup_organization_variable_bindings.ts`](../src/application/policies/setup_organization_variable_bindings.ts) · [`src/application/policies/setup_organization_variable_reuse_policy.ts`](../src/application/policies/setup_organization_variable_reuse_policy.ts) · [`src/application/ports/setup_batch_ports.ts`](../src/application/ports/setup_batch_ports.ts) · [`src/application/usecases/setup/apply_setup_batch_use_case.ts`](../src/application/usecases/setup/apply_setup_batch_use_case.ts) · [`src/application/usecases/setup/configure_setup_batch_target_use_case.ts`](../src/application/usecases/setup/configure_setup_batch_target_use_case.ts) · [`src/application/usecases/setup/prepare_setup_batch_use_case.ts`](../src/application/usecases/setup/prepare_setup_batch_use_case.ts) · [`src/application/usecases/setup/select_setup_batch_targets_use_case.ts`](../src/application/usecases/setup/select_setup_batch_targets_use_case.ts) · [`src/application/usecases/setup/setup_batch_session_use_case.ts`](../src/application/usecases/setup/setup_batch_session_use_case.ts) · [`src/cli/setup_batch_adapter.ts`](../src/cli/setup_batch_adapter.ts) · [`src/cli/setup_source_adapter.ts`](../src/cli/setup_source_adapter.ts) · [`src/domain/setup_batch.ts`](../src/domain/setup_batch.ts) · [`src/domain/setup_credential_health.ts`](../src/domain/setup_credential_health.ts) · [`src/domain/setup_organization_reuse.ts`](../src/domain/setup_organization_reuse.ts) · [`src/infrastructure/composition/setup_batch_composition_root.ts`](../src/infrastructure/composition/setup_batch_composition_root.ts) · [`src/infrastructure/github/ports/github_setup_batch_protocol.ts`](../src/infrastructure/github/ports/github_setup_batch_protocol.ts) · [`src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts`](../src/infrastructure/github/ports/github_trusted_credential_health_protocol.ts) · [`src/infrastructure/setup_batch_github_apply_adapter.ts`](../src/infrastructure/setup_batch_github_apply_adapter.ts) · [`src/infrastructure/setup_batch_github_read_adapter.ts`](../src/infrastructure/setup_batch_github_read_adapter.ts) · [`src/infrastructure/setup_batch_journal_adapter.ts`](../src/infrastructure/setup_batch_journal_adapter.ts) · [`src/infrastructure/setup_batch_workspace_adapter.ts`](../src/infrastructure/setup_batch_workspace_adapter.ts) · [`src/infrastructure/setup_trusted_credential_health_adapter.ts`](../src/infrastructure/setup_trusted_credential_health_adapter.ts) · [`src/application/policies/setup_remote_facts_policy.ts`](../src/application/policies/setup_remote_facts_policy.ts) · [`src/cli/setup_confirmation_adapter.ts`](../src/cli/setup_confirmation_adapter.ts) · [`src/cli/web_setup_adapters.ts`](../src/cli/web_setup_adapters.ts) · [`scripts/validate-agent-documentation.cjs`](../scripts/validate-agent-documentation.cjs) · [`src/utils/setup_workflow_upgrade.ts`](../src/utils/setup_workflow_upgrade.ts) · [`src/domain/setup_runner.ts`](../src/domain/setup_runner.ts) · [`src/application/policies/setup_runner_policy.ts`](../src/application/policies/setup_runner_policy.ts) · [`src/application/policies/setup_runner_question_policy.ts`](../src/application/policies/setup_runner_question_policy.ts) · [`src/application/policies/setup_runner_guidance_policy.ts`](../src/application/policies/setup_runner_guidance_policy.ts) · [`src/application/ports/setup_runner_ports.ts`](../src/application/ports/setup_runner_ports.ts) · [`src/infrastructure/setup_runner_discovery_adapter.ts`](../src/infrastructure/setup_runner_discovery_adapter.ts) · [`src/infrastructure/composition/setup_runner_composition_root.ts`](../src/infrastructure/composition/setup_runner_composition_root.ts) · [`src/utils/setup_installation_review.ts`](../src/utils/setup_installation_review.ts) · [`src/cli/setup_installation_review_presenter.ts`](../src/cli/setup_installation_review_presenter.ts) · [`src/application/policies/setup_runner_installation_summary_policy.ts`](../src/application/policies/setup_runner_installation_summary_policy.ts) · [`src/utils/setup_files.ts`](../src/utils/setup_files.ts) · [`src/domain/specification_catalog_path_policy.cjs`](../src/domain/specification_catalog_path_policy.cjs) · [`src/domain/sdd_prerequisite_policy.cjs`](../src/domain/sdd_prerequisite_policy.cjs) · [`src/application/contracts/setup_documentation.ts`](../src/application/contracts/setup_documentation.ts) · [`src/application/policies/setup_question_documentation_policy.ts`](../src/application/policies/setup_question_documentation_policy.ts) · [`web/src/components/SetupHelp.svelte`](../web/src/components/SetupHelp.svelte) · [`web/src/components/CompletedSetupHelp.svelte`](../web/src/components/CompletedSetupHelp.svelte) · [`src/application/policies/verification_runtime_policy.ts`](../src/application/policies/verification_runtime_policy.ts) · [`src/infrastructure/verification_runtime_manifest.ts`](../src/infrastructure/verification_runtime_manifest.ts) · [`src/infrastructure/verification_runtime_preflight.ts`](../src/infrastructure/verification_runtime_preflight.ts) · [`src/infrastructure/untrusted_command_environment.ts`](../src/infrastructure/untrusted_command_environment.ts) · [`src/application/ports/git_ports.ts`](../src/application/ports/git_ports.ts) · [`src/application/ports/bugbot_git_ports.ts`](../src/application/ports/bugbot_git_ports.ts) · [`src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts`](../src/application/usecases/steps/commit/bugbot/commit_and_push_preflight.ts) · [`src/application/usecases/steps/commit/bugbot/verify_command_runner.ts`](../src/application/usecases/steps/commit/bugbot/verify_command_runner.ts) · [`src/cli/setup_config_file.ts`](../src/cli/setup_config_file.ts) · [`src/domain/copilot_comment_workflow.ts`](../src/domain/copilot_comment_workflow.ts) · [`src/infrastructure/copilot_comment_inputs.json`](../src/infrastructure/copilot_comment_inputs.json) -- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) · [`src/domain/__tests__/github_runtime_doctor.test.ts`](../src/domain/__tests__/github_runtime_doctor.test.ts) · [`src/domain/__tests__/copilot_command.test.ts`](../src/domain/__tests__/copilot_command.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_policy.test.ts) · [`src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts) · [`src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts`](../src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts) · [`src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts`](../src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts) · [`src/data/repository/github/__tests__/runtime_doctor_repository.test.ts`](../src/data/repository/github/__tests__/runtime_doctor_repository.test.ts) · [`src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts`](../src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts) · [`src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts`](../src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts) · [`src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts) · [`src/application/policies/__tests__/setup_approval_operator_permissions.test.ts`](../src/application/policies/__tests__/setup_approval_operator_permissions.test.ts) · [`src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts`](../src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts) · [`src/application/policies/__tests__/setup_batch_permission_policy.test.ts`](../src/application/policies/__tests__/setup_batch_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_policy.test.ts`](../src/application/policies/__tests__/setup_batch_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_retry_policy.test.ts`](../src/application/policies/__tests__/setup_batch_retry_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_selection_policy.test.ts`](../src/application/policies/__tests__/setup_batch_selection_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_approval_draft.test.ts`](../src/application/policies/__tests__/setup_organization_approval_draft.test.ts) · [`src/application/policies/__tests__/setup_organization_import_policy.test.ts`](../src/application/policies/__tests__/setup_organization_import_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_import_safety.test.ts`](../src/application/policies/__tests__/setup_organization_import_safety.test.ts) · [`src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts`](../src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts) · [`src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts) · [`src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts`](../src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts) · [`src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts`](../src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_batch_flow.test.ts`](../src/application/usecases/setup/__tests__/setup_batch_flow.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts) · [`src/cli/__tests__/setup_source_adapter.test.ts`](../src/cli/__tests__/setup_source_adapter.test.ts) · [`src/cli/__tests__/web_setup_batch_components.test.ts`](../src/cli/__tests__/web_setup_batch_components.test.ts) · [`src/cli/__tests__/web_setup_batch_selection.test.ts`](../src/cli/__tests__/web_setup_batch_selection.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_local_adapters.test.ts`](../src/infrastructure/__tests__/setup_batch_local_adapters.test.ts) · [`src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts`](../src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts) · [`src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts`](../src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts) · [`src/application/policies/__tests__/setup_batch_access_and_view.test.ts`](../src/application/policies/__tests__/setup_batch_access_and_view.test.ts) · [`src/cli/__tests__/setup_batch_adapter.test.ts`](../src/cli/__tests__/setup_batch_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_composition.test.ts`](../src/infrastructure/__tests__/setup_batch_composition.test.ts) · [`src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts`](../src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts) · [`src/utils/__tests__/setup_workflow_upgrade.test.ts`](../src/utils/__tests__/setup_workflow_upgrade.test.ts) · [`src/cli/__tests__/setup_pat_intent_adapter.test.ts`](../src/cli/__tests__/setup_pat_intent_adapter.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/policies/__tests__/setup_runner_policy.test.ts`](../src/application/policies/__tests__/setup_runner_policy.test.ts) · [`src/application/policies/__tests__/setup_runner_question_policy.test.ts`](../src/application/policies/__tests__/setup_runner_question_policy.test.ts) · [`src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts`](../src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts) · [`src/utils/__tests__/setup_runner_installation.test.ts`](../src/utils/__tests__/setup_runner_installation.test.ts) · [`src/cli/__tests__/web_setup_runner_components.test.ts`](../src/cli/__tests__/web_setup_runner_components.test.ts) · [`src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts`](../src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_runner_planning.test.ts`](../src/application/usecases/setup/__tests__/setup_runner_planning.test.ts) · [`src/cli/__tests__/setup_runner_config_file.test.ts`](../src/cli/__tests__/setup_runner_config_file.test.ts) · [`src/infrastructure/__tests__/setup_runner_composition.test.ts`](../src/infrastructure/__tests__/setup_runner_composition.test.ts) · [`src/tooling/__tests__/setup_provisioning_workflow.test.ts`](../src/tooling/__tests__/setup_provisioning_workflow.test.ts) · [`src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts`](../src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts) · [`src/cli/__tests__/setup_documentation_navigation.test.ts`](../src/cli/__tests__/setup_documentation_navigation.test.ts) · [`src/application/policies/__tests__/setup_question_documentation_policy.test.ts`](../src/application/policies/__tests__/setup_question_documentation_policy.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) · [`src/infrastructure/__tests__/verification_setup_configuration.test.ts`](../src/infrastructure/__tests__/verification_setup_configuration.test.ts) · [`src/domain/__tests__/copilot_comment_workflow.test.ts`](../src/domain/__tests__/copilot_comment_workflow.test.ts) +- Tests: [`src/data/repository/__tests__/repository_version_tags_query_repository.test.ts`](../src/data/repository/__tests__/repository_version_tags_query_repository.test.ts) · [`src/cli/__tests__/setup_command_options.test.ts`](../src/cli/__tests__/setup_command_options.test.ts) · [`src/data/repository/__tests__/setup_variable_write_failures.test.ts`](../src/data/repository/__tests__/setup_variable_write_failures.test.ts) · [`src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts`](../src/infrastructure/composition/__tests__/initial_setup_composition_root.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/setup_configuration_policy.test.ts`](../src/application/policies/__tests__/setup_configuration_policy.test.ts) · [`src/application/policies/__tests__/setup_token_permission_policy.test.ts`](../src/application/policies/__tests__/setup_token_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_remote_facts_policy.test.ts`](../src/application/policies/__tests__/setup_remote_facts_policy.test.ts) · [`src/application/policies/__tests__/setup_doctor_message_catalog.test.ts`](../src/application/policies/__tests__/setup_doctor_message_catalog.test.ts) · [`src/application/policies/__tests__/setup_doctor_report_policy.test.ts`](../src/application/policies/__tests__/setup_doctor_report_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts`](../src/application/usecases/setup/__tests__/setup_questionnaire_controller.test.ts) · [`src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_wizard_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_journey_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts`](../src/application/usecases/setup/__tests__/setup_session_coordinator.test.ts) · [`src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_credentials_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts`](../src/application/usecases/setup/__tests__/setup_token_permissions_use_case.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts`](../src/application/usecases/setup/__tests__/merge_queue_readiness_use_case.test.ts) · [`src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts`](../src/application/usecases/actions/__tests__/initial_setup_use_case.test.ts) · [`src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_resource_provisioning.test.ts) · [`src/infrastructure/__tests__/setup_workspace_adapter.test.ts`](../src/infrastructure/__tests__/setup_workspace_adapter.test.ts) · [`src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_remote_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_token_permission_query_adapter.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_probes.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_probes.test.ts) · [`src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts`](../src/infrastructure/__tests__/setup_projects_probe_page_policy.test.ts) · [`src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts`](../src/infrastructure/composition/__tests__/setup_token_permissions_composition_root.test.ts) · [`src/data/repository/__tests__/repository_variables_repository.test.ts`](../src/data/repository/__tests__/repository_variables_repository.test.ts) · [`src/cli/__tests__/setup_presenters.test.ts`](../src/cli/__tests__/setup_presenters.test.ts) · [`src/cli/__tests__/setup_journey_presenter.test.ts`](../src/cli/__tests__/setup_journey_presenter.test.ts) · [`src/cli/__tests__/setup_prompt_rendering.test.ts`](../src/cli/__tests__/setup_prompt_rendering.test.ts) · [`src/cli/__tests__/setup_token_permission_presenter.test.ts`](../src/cli/__tests__/setup_token_permission_presenter.test.ts) · [`src/__tests__/cli.test.ts`](../src/__tests__/cli.test.ts) · [`src/cli/__tests__/setup_terminal_driver.test.ts`](../src/cli/__tests__/setup_terminal_driver.test.ts) · [`src/architecture/__tests__/setup_doctor_boundaries.test.ts`](../src/architecture/__tests__/setup_doctor_boundaries.test.ts) · [`src/tooling/__tests__/documentation_pat_exception_policy.test.ts`](../src/tooling/__tests__/documentation_pat_exception_policy.test.ts) · [`src/infrastructure/__tests__/setup_pat_audit.test.ts`](../src/infrastructure/__tests__/setup_pat_audit.test.ts) · [`src/infrastructure/__tests__/setup_permission_projects_access.test.ts`](../src/infrastructure/__tests__/setup_permission_projects_access.test.ts) · [`src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts`](../src/infrastructure/__tests__/setup_credential_validation_adapter.test.ts) · [`src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts`](../src/infrastructure/__tests__/setup_github_identity_query_adapter.test.ts) · [`src/infrastructure/__tests__/git_commit_adapter.test.ts`](../src/infrastructure/__tests__/git_commit_adapter.test.ts) · [`src/actions/__tests__/github_action_input.test.ts`](../src/actions/__tests__/github_action_input.test.ts) · [`src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_cleanup_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts`](../src/infrastructure/__tests__/setup_permission_actions_cleanup_timing.test.ts) · [`src/infrastructure/__tests__/setup_permission_operator_actions.test.ts`](../src/infrastructure/__tests__/setup_permission_operator_actions.test.ts) · [`src/infrastructure/__tests__/setup_permission_journal_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_journal_contract.test.ts) · [`src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts`](../src/infrastructure/__tests__/setup_permission_audit_transactions.test.ts) · [`src/infrastructure/__tests__/setup_permission_write_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_write_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_resource_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_resource_failures.test.ts) · [`src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts`](../src/infrastructure/__tests__/setup_permission_boundary_failures.test.ts) · [`src/infrastructure/__tests__/github_secret_encryption.test.ts`](../src/infrastructure/__tests__/github_secret_encryption.test.ts) · [`src/infrastructure/__tests__/setup_permission_organization_owner.test.ts`](../src/infrastructure/__tests__/setup_permission_organization_owner.test.ts) · [`src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts`](../src/infrastructure/__tests__/setup_pat_bootstrap_transactions.test.ts) · [`src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts`](../src/data/repository/__tests__/repository_actions_sdk_contract.integration.test.ts) · [`src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts`](../src/data/repository/__tests__/repository_actions_resource_edge_cases.test.ts) · [`src/data/repository/__tests__/repository_selected_resource_grants.test.ts`](../src/data/repository/__tests__/repository_selected_resource_grants.test.ts) · [`src/infrastructure/__tests__/setup_permission_read_contract.test.ts`](../src/infrastructure/__tests__/setup_permission_read_contract.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_conflict.test.ts) · [`src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts`](../src/application/usecases/setup/__tests__/setup_workflow_pat_credential_rotation.test.ts) · [`src/data/repository/__tests__/setup_organization_pat_inventory.test.ts`](../src/data/repository/__tests__/setup_organization_pat_inventory.test.ts) · [`src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts`](../src/cli/__tests__/web_setup_bot_pat_scope_conflict.test.ts) · [`src/actions/__tests__/local_setup_apply_contract.test.ts`](../src/actions/__tests__/local_setup_apply_contract.test.ts) · [`src/cli/commands/__tests__/setup_policy.test.ts`](../src/cli/commands/__tests__/setup_policy.test.ts) · [`src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts`](../src/application/usecases/execution/__tests__/execution_issue_number_policy.test.ts) · [`src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts`](../src/application/usecases/execution/__tests__/setup_execution_workflow.test.ts) · [`src/domain/__tests__/github_runtime_doctor.test.ts`](../src/domain/__tests__/github_runtime_doctor.test.ts) · [`src/domain/__tests__/copilot_command.test.ts`](../src/domain/__tests__/copilot_command.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_policy.test.ts) · [`src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_condition_policy.test.ts) · [`src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts`](../src/application/policies/__tests__/github_runtime_doctor_presentation_policy.test.ts) · [`src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts`](../src/application/usecases/__tests__/read_copilot_doctor_workflow.test.ts) · [`src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts`](../src/application/usecases/__tests__/runtime_doctor_security_integration.test.ts) · [`src/data/repository/github/__tests__/runtime_doctor_repository.test.ts`](../src/data/repository/github/__tests__/runtime_doctor_repository.test.ts) · [`src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts`](../src/infrastructure/composition/__tests__/github_runtime_doctor_composition_root.test.ts) · [`src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts`](../src/application/policies/__tests__/runtime_doctor_agent_activity.test.ts) · [`src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts`](../src/application/policies/__tests__/doctor_workflow_matrix_policy.test.ts) · [`src/application/policies/__tests__/setup_approval_operator_permissions.test.ts`](../src/application/policies/__tests__/setup_approval_operator_permissions.test.ts) · [`src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts`](../src/application/usecases/setup/__tests__/audit_configured_setup_pat_use_case.test.ts) · [`src/application/policies/__tests__/setup_batch_permission_policy.test.ts`](../src/application/policies/__tests__/setup_batch_permission_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_policy.test.ts`](../src/application/policies/__tests__/setup_batch_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_retry_policy.test.ts`](../src/application/policies/__tests__/setup_batch_retry_policy.test.ts) · [`src/application/policies/__tests__/setup_batch_selection_policy.test.ts`](../src/application/policies/__tests__/setup_batch_selection_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_approval_draft.test.ts`](../src/application/policies/__tests__/setup_organization_approval_draft.test.ts) · [`src/application/policies/__tests__/setup_organization_import_policy.test.ts`](../src/application/policies/__tests__/setup_organization_import_policy.test.ts) · [`src/application/policies/__tests__/setup_organization_import_safety.test.ts`](../src/application/policies/__tests__/setup_organization_import_safety.test.ts) · [`src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts`](../src/application/policies/__tests__/setup_organization_variable_reuse_policy.test.ts) · [`src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts`](../src/application/usecases/actions/__tests__/setup_organization_reuse_provisioning.test.ts) · [`src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts`](../src/application/usecases/setup/__tests__/apply_setup_batch_use_case.test.ts) · [`src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts`](../src/application/usecases/setup/__tests__/select_setup_batch_targets_use_case.test.ts) · [`src/application/usecases/setup/__tests__/setup_batch_flow.test.ts`](../src/application/usecases/setup/__tests__/setup_batch_flow.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_import_wizard.test.ts) · [`src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts`](../src/application/usecases/setup/__tests__/setup_organization_secret_reuse.test.ts) · [`src/cli/__tests__/setup_source_adapter.test.ts`](../src/cli/__tests__/setup_source_adapter.test.ts) · [`src/cli/__tests__/web_setup_batch_components.test.ts`](../src/cli/__tests__/web_setup_batch_components.test.ts) · [`src/cli/__tests__/web_setup_batch_selection.test.ts`](../src/cli/__tests__/web_setup_batch_selection.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_apply_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts`](../src/infrastructure/__tests__/setup_batch_github_read_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_local_adapters.test.ts`](../src/infrastructure/__tests__/setup_batch_local_adapters.test.ts) · [`src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts`](../src/infrastructure/__tests__/setup_reuse_empty_collections.integration.test.ts) · [`src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts`](../src/infrastructure/__tests__/setup_trusted_credential_health_adapter.test.ts) · [`src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts`](../src/infrastructure/__tests__/setup_trusted_health_sdk_contract.integration.test.ts) · [`src/application/policies/__tests__/setup_batch_access_and_view.test.ts`](../src/application/policies/__tests__/setup_batch_access_and_view.test.ts) · [`src/cli/__tests__/setup_batch_adapter.test.ts`](../src/cli/__tests__/setup_batch_adapter.test.ts) · [`src/infrastructure/__tests__/setup_batch_composition.test.ts`](../src/infrastructure/__tests__/setup_batch_composition.test.ts) · [`src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts`](../src/infrastructure/__tests__/setup_batch_guidance_compatibility.test.ts) · [`src/utils/__tests__/setup_workflow_upgrade.test.ts`](../src/utils/__tests__/setup_workflow_upgrade.test.ts) · [`src/cli/__tests__/setup_pat_intent_adapter.test.ts`](../src/cli/__tests__/setup_pat_intent_adapter.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/policies/__tests__/setup_runner_policy.test.ts`](../src/application/policies/__tests__/setup_runner_policy.test.ts) · [`src/application/policies/__tests__/setup_runner_question_policy.test.ts`](../src/application/policies/__tests__/setup_runner_question_policy.test.ts) · [`src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts`](../src/infrastructure/__tests__/setup_runner_discovery_adapter.test.ts) · [`src/utils/__tests__/setup_runner_installation.test.ts`](../src/utils/__tests__/setup_runner_installation.test.ts) · [`src/cli/__tests__/web_setup_runner_components.test.ts`](../src/cli/__tests__/web_setup_runner_components.test.ts) · [`src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts`](../src/application/policies/__tests__/setup_runner_installation_summary_policy.test.ts) · [`src/application/usecases/setup/__tests__/setup_runner_planning.test.ts`](../src/application/usecases/setup/__tests__/setup_runner_planning.test.ts) · [`src/cli/__tests__/setup_runner_config_file.test.ts`](../src/cli/__tests__/setup_runner_config_file.test.ts) · [`src/infrastructure/__tests__/setup_runner_composition.test.ts`](../src/infrastructure/__tests__/setup_runner_composition.test.ts) · [`src/tooling/__tests__/setup_provisioning_workflow.test.ts`](../src/tooling/__tests__/setup_provisioning_workflow.test.ts) · [`src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts`](../src/infrastructure/__tests__/setup_sdd_prerequisites.test.ts) · [`src/cli/__tests__/setup_documentation_navigation.test.ts`](../src/cli/__tests__/setup_documentation_navigation.test.ts) · [`src/application/policies/__tests__/setup_question_documentation_policy.test.ts`](../src/application/policies/__tests__/setup_question_documentation_policy.test.ts) · [`src/application/policies/__tests__/verification_runtime_policy.test.ts`](../src/application/policies/__tests__/verification_runtime_policy.test.ts) · [`src/infrastructure/__tests__/verification_runtime_preflight.test.ts`](../src/infrastructure/__tests__/verification_runtime_preflight.test.ts) · [`src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts`](../src/infrastructure/__tests__/verification_runtime_branch_sync.test.ts) · [`src/infrastructure/__tests__/untrusted_command_environment.test.ts`](../src/infrastructure/__tests__/untrusted_command_environment.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/verify_command_runner.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts) · [`src/tooling/__tests__/verification_runtime_guidance.test.ts`](../src/tooling/__tests__/verification_runtime_guidance.test.ts) · [`src/infrastructure/__tests__/verification_setup_configuration.test.ts`](../src/infrastructure/__tests__/verification_setup_configuration.test.ts) · [`src/domain/__tests__/copilot_comment_workflow.test.ts`](../src/domain/__tests__/copilot_comment_workflow.test.ts) · [`src/tooling/__tests__/credential_health_transport.test.ts`](../src/tooling/__tests__/credential_health_transport.test.ts) - User documentation: [`README.md`](../README.md) · [`docs/how-to-use.mdx`](../docs/how-to-use.mdx) · [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/authentication.mdx`](../docs/authentication.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) · [`docs/security-operations/operations/provisioning.mdx`](../docs/security-operations/operations/provisioning.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/security-operations/security/credentials.mdx`](../docs/security-operations/security/credentials.mdx) · [`docs/single-actions/workflow-and-cli.mdx`](../docs/single-actions/workflow-and-cli.mdx) · [`docs/security-operations/operations/verification.mdx`](../docs/security-operations/operations/verification.mdx) · [`docs/development/setup-pat-provider-audit-2026-10-06.md`](../docs/development/setup-pat-provider-audit-2026-10-06.md) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/setup-web.mdx`](../docs/setup-web.mdx) · [`docs/first-workflow.mdx`](../docs/first-workflow.mdx) · [`docs/using-the-docs.mdx`](../docs/using-the-docs.mdx) · [`docs/guides/index.mdx`](../docs/guides/index.mdx) · [`docs/guides/issues-and-branches.mdx`](../docs/guides/issues-and-branches.mdx) · [`docs/guides/review-pull-requests.mdx`](../docs/guides/review-pull-requests.mdx) · [`docs/guides/work-with-the-bot.mdx`](../docs/guides/work-with-the-bot.mdx) · [`docs/guides/releases-and-hotfixes.mdx`](../docs/guides/releases-and-hotfixes.mdx) · [`docs/agents/repository-collaboration.mdx`](../docs/agents/repository-collaboration.mdx) ### `local-web-setup-assistant` — Local web setup assistant diff --git a/specs/catalog.json b/specs/catalog.json index 812928ad6..f645accf8 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -754,7 +754,7 @@ "status": "implemented", "scope": "Plan, validate, provision, and audit a repository installation without exposing credentials", "owner": "Copilot maintainers", - "lastVerified": "2026-10-20", + "lastVerified": "2026-10-24", "specs": [ "specs/setup-configuration-credentials-and-doctor.md", "specs/setup-doctor-architecture-hardening.md", @@ -1078,7 +1078,8 @@ "src/application/usecases/steps/commit/bugbot/__tests__/commit_verification_workspace.test.ts", "src/tooling/__tests__/verification_runtime_guidance.test.ts", "src/infrastructure/__tests__/verification_setup_configuration.test.ts", - "src/domain/__tests__/copilot_comment_workflow.test.ts" + "src/domain/__tests__/copilot_comment_workflow.test.ts", + "src/tooling/__tests__/credential_health_transport.test.ts" ], "documentation": [ "README.md", diff --git a/specs/setup-configuration-credentials-and-doctor.md b/specs/setup-configuration-credentials-and-doctor.md index abd51b16b..3ee9aa55c 100644 --- a/specs/setup-configuration-credentials-and-doctor.md +++ b/specs/setup-configuration-credentials-and-doctor.md @@ -2,7 +2,7 @@ - Status: Implemented — automated architecture, UX, documentation, and coverage gates complete; controlled live GitHub permission-path evidence remains external - Date: 2026-09-11 -- Last updated: 2026-10-09 +- Last updated: 2026-10-24 - Catalog capability ID: `setup-and-doctor` - Last verified: 2026-10-09 (PR #452 per-role override and native Metadata-response regressions; prior shared-session, resource-progress and repository-only Apply fixture evidence remains applicable; live GitHub path remains external) - Owners: Copilot maintainers @@ -488,6 +488,79 @@ validated before write, and represented thereafter only by name/status. Secret and Variable scopes are independent. The product MUST not expose values through plan objects, errors, command history suggestions, backups, or doctor output. +### Credential health transport hardening (issue #682, 2026-10-24) + +**Observed defect:** generated health jobs put provider/PAT values in curl argv; +Google also put the value in a query string. Shared self-hosted processes can +inspect arguments independently of GitHub's log masking. This was observed in +GeckoCV PR #21 and is present in both source workflow surfaces. + +**Required behavior:** health checks MUST retain their existing explicit input, +requested-secret-only environment, read-only endpoints, job IDs and timeout. +Each request MUST deliver its sensitive authentication header over stdin via +curl's `--header @-`; neither raw nor Basic-encoded credentials may enter argv +or URLs. Cursor retains Basic authentication, with encoding performed through +stdin. Google uses the provider-supported `x-goog-api-key` header. Empty values +and CR or LF MUST fail before HTTP. curl MUST disable user curlrc processing, +require HTTPS, keep its 20-second limit and reject redirects before forwarding +custom secret headers. A failed request MUST fail the job; PAT's second check +MUST NOT run after its first check fails. No additional provider, request retry, +permission, configuration option or inferred health dispatch is introduced. + +```text +Explicit check flag -> requested Secret in job environment -> validate value + -> authentication headers over stdin -> bounded HTTPS request -> job status +``` + +Text equivalent: the operator selects a check, only its requested Secret enters +that job, validation runs before HTTP, authentication travels on stdin and the +bounded request determines the existing job status. The shell and runner remain +trusted to hold the requested value in memory/environment; this does not add +OS-level isolation between processes running as the same account. + +This is a transport correction in the distributed workflow adapter, independent +of the frontend-neutral setup/doctor use cases, health ports and orchestration. +The narrow Actions permission probe pins the exact reviewed packaged health +workflow digest. Its pin is updated with this asset; an unexpected remote file +still uses the existing disabled-job override instead of trusted direct dispatch. +No durable state or public schema changes. Existing installations must update +the managed health workflow as part of a reviewed setup upgrade; backup and +preservation rules remain unchanged. A source merge does not update copied +consumer files. Rollback restores the preceding workflow and its old behavior. + +The user-visible job names and default-disabled inputs remain unchanged. +Representative GitHub states are `Verify PAT: queued`, `Verify PAT: in progress` +and a successful check. Validation details contain only `Required credential is +missing.` or `Credential contains invalid line breaks.`; failed HTTP requests +retain curl's bounded failure status and never print a response body or key. +There are no additional issue comments, notifications or persisted secret files. +The operator corrects the named Secret, or reviews a changed canonical provider +endpoint when a redirect is rejected, before explicitly retrying the check. + +**Additional test floor: 82 distinct cases**, independent of existing budgets: +80 black-box shell cases (8 credential types × 2 source workflows × valid, +missing, CR, LF and HTTP-failure states), plus 2 structural opt-in/permission/ +secret-scope cases. A fake curl records actual argv and stdin without real +credentials or network. Valid cases assert authentication arrives, both raw and +Basic values are absent from argv, HTTPS/time/redirect bounds remain, output is +value-free and PAT checks both endpoints. Negative cases assert zero HTTP for +invalid input and one request/no retry on transport failure. Shell cases run on +Unix; Windows retains both structural cases because canonical health execution +is on Ubuntu. Existing repository coverage and native platform gates remain. + +Acceptance/traceability: argv/URL confidentiality, header authenticity and request +bounds map to `credential_health_transport.test.ts` positive cases; validation +and failure propagation map to its negative cases; opt-in and privilege bounds +map to its structural cases. The existing workflow validator, setup/doctor +adapter tests and catalog checks retain their boundaries. Documentation lives in +`docs/security-operations/security/credentials.mdx`. Definition of Done includes +these 82 cases, generated/source workflow parity, build/lint/type/docs/spec gates, +consumer adoption and current-head review without unresolved findings. + +Primary references: [curl header stdin and redirect behavior](https://github.com/curl/curl/blob/master/docs/cmdline-opts/header.md) +and [Gemini header authentication](https://ai.google.dev/gemini-api/docs/api-key). + + ## 12. Observability and operational UX Setup reports the plan, credential checks, merge-queue checks, file decisions, diff --git a/src/infrastructure/setup_permission_actions_probe.ts b/src/infrastructure/setup_permission_actions_probe.ts index 5b6aa63da..d1f5baf6b 100644 --- a/src/infrastructure/setup_permission_actions_probe.ts +++ b/src/infrastructure/setup_permission_actions_probe.ts @@ -9,7 +9,7 @@ const MAX_WORKFLOW_FILE_CHECKS = 64; // Hash of the packaged credential-health workflow: every job is guarded by a // false-by-default input. A different remote file is never dispatched directly. -const TRUSTED_HEALTH_WORKFLOW_SHA256 = '7fa36bf72d144df6fe2ccc45b805ad442187aa6979dfda71446a54f607b18d61'; +const TRUSTED_HEALTH_WORKFLOW_SHA256 = '5f093b2c03bef389ccea68ef43bec3577cb1bfddc038da1884a146088f39a4ec'; /** Dispatches a disabled-job branch override of a workflow already registered on default. */ export async function probeActions(context: ResourceProbeContext): Promise { diff --git a/src/tooling/__tests__/credential_health_transport.test.ts b/src/tooling/__tests__/credential_health_transport.test.ts new file mode 100644 index 000000000..89901b453 --- /dev/null +++ b/src/tooling/__tests__/credential_health_transport.test.ts @@ -0,0 +1,139 @@ +import { spawnSync } from 'node:child_process'; +import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { delimiter, dirname, join, resolve } from 'node:path'; +import { load } from 'js-yaml'; + +type HealthStep = { env: Record; run: string }; +type HealthJob = { if: string; steps: HealthStep[] }; +type HealthWorkflow = { + on: { workflow_dispatch: { inputs: Record } }; + permissions: Record; + jobs: Record; +}; +type RequestTrace = { args: string[]; input: string }; +const root = resolve(__dirname, '../../..'); +const unixIt = process.platform === 'win32' ? it.skip : it; +const endpoints: Record = { + 'verify-pat': ['https://api.github.com/user', 'https://api.github.com/repos/fixture/credential-health'], + 'verify-openai': ['https://api.openai.com/v1/models'], + 'verify-anthropic': ['https://api.anthropic.com/v1/models'], + 'verify-google': ['https://generativelanguage.googleapis.com/v1beta/models'], + 'verify-openrouter': ['https://openrouter.ai/api/v1/models'], + 'verify-cursor': ['https://api.cursor.com/analytics/ai-code/changes?startDate=30d&page=1&pageSize=1'], + 'verify-opencode': ['https://opencode.ai/zen/v1/models'], + 'verify-codex-api-key': ['https://api.openai.com/v1/models'], +}; +const fixtures = [ + ['verify-pat', 'PAT', 'check_pat'], + ['verify-openai', 'OPENAI_API_KEY', 'check_openai'], + ['verify-anthropic', 'ANTHROPIC_API_KEY', 'check_anthropic'], + ['verify-google', 'GOOGLE_API_KEY', 'check_google'], + ['verify-openrouter', 'OPENROUTER_API_KEY', 'check_openrouter'], + ['verify-cursor', 'CURSOR_API_KEY', 'check_cursor'], + ['verify-opencode', 'OPENCODE_API_KEY', 'check_opencode'], + ['verify-codex-api-key', 'CODEX_API_KEY', 'check_codex_api_key'], +] as const; + +function execute(script: string, key: string, value: string, status = 0) { + const directory = mkdtempSync(join(tmpdir(), 'credential-health-transport-')); + const trace = join(directory, 'trace.jsonl'); + const curl = join(directory, 'curl'); + writeFileSync(curl, `#!/usr/bin/env node +const fs = require('node:fs'); +let input = ''; +process.stdin.setEncoding('utf8'); +process.stdin.on('data', chunk => { input += chunk; }); +process.stdin.on('end', () => { + fs.appendFileSync(process.env.CURL_TRACE, JSON.stringify({args: process.argv.slice(2), input}) + '\\n'); + process.exitCode = Number(process.env.CURL_STATUS); +}); +`); + chmodSync(curl, 0o700); + try { + const result = spawnSync('/bin/bash', ['--noprofile', '--norc', '-c', script], { + encoding: 'utf8', timeout: 5_000, + env: { + PATH: [directory, dirname(process.execPath), '/usr/bin', '/bin'].join(delimiter), + HOME: directory, GITHUB_REPOSITORY: 'fixture/credential-health', + CURL_TRACE: trace, CURL_STATUS: String(status), [key]: value, + }, + }); + const requests: RequestTrace[] = existsSync(trace) + ? readFileSync(trace, 'utf8').trim().split('\n').map(line => JSON.parse(line) as RequestTrace) : []; + return { ...result, requests }; + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +describe.each([ + 'setup/workflows/copilot_credential_health.yml', + '.github/workflows/copilot_credential_health.yml', +])('credential transport in %s', file => { + const workflow = load(readFileSync(join(root, file), 'utf8')) as HealthWorkflow; + + it('keeps every credential check explicit, disabled by default and scoped to its requested secret', () => { + expect(Object.keys(workflow.on)).toEqual(['workflow_dispatch']); + expect(workflow.permissions).toEqual({ contents: 'read' }); + expect(Object.keys(workflow.jobs).sort()).toEqual(fixtures.map(([id]) => id).sort()); + for (const [id, key, input] of fixtures) { + expect(workflow.on.workflow_dispatch.inputs[input].default).toBe(false); + expect(workflow.jobs[id].if).toBe(`\${{ inputs.${input} }}`); + expect(workflow.jobs[id].steps).toHaveLength(1); + expect(workflow.jobs[id].steps[0].env).toEqual({ [key]: `\${{ secrets.${key} }}` }); + } + }); + + unixIt.each(fixtures)('%s authenticates over stdin without raw or Basic secrets in argv', (id, key) => { + const value = 'synthetic-value-with:quotes"and\\slashes'; + const encoded = Buffer.from(`${value}:`).toString('base64'); + const result = execute(workflow.jobs[id].steps[0].run, key, value); + expect(result.error).toBeUndefined(); + expect(result.status).toBe(0); + expect(result.stdout + result.stderr).not.toContain(value); + expect(result.stdout + result.stderr).not.toContain(encoded); + expect(result.requests.map(request => request.args.at(-1))).toEqual(endpoints[id]); + expect(result.requests).toHaveLength(key === 'PAT' ? 2 : 1); + for (const request of result.requests) { + expect(request.args.join(' ')).not.toContain(value); + expect(request.args.join(' ')).not.toContain(encoded); + expect(request.args[0]).toBe('-q'); + expect(request.args).toEqual(expect.arrayContaining([ + '--fail', '--max-time', '20', '--header', '@-', + '--location', '--max-redirs', '0', '--proto', '=https', '--proto-redir', '=https', + ])); + const header = key === 'GOOGLE_API_KEY' ? `x-goog-api-key: ${value}\n` + : key === 'ANTHROPIC_API_KEY' ? `x-api-key: ${value}\n` + : key === 'CURSOR_API_KEY' ? `Authorization: Basic ${encoded}\n` + : `Authorization: Bearer ${value}\n`; + expect(request.input).toBe(header); + expect(request.args.at(-1)).toMatch(/^https:\/\//u); + expect(request.args.at(-1)).not.toContain('?key='); + } + }); + + unixIt.each(fixtures)('%s stops before HTTP when its required credential is missing', (id, key) => { + const result = execute(workflow.jobs[id].steps[0].run, key, ''); + expect(result.status).toBe(1); + expect(result.requests).toEqual([]); + expect(result.stderr).toContain('Required credential is missing.'); + }); + + unixIt.each(fixtures.flatMap(([id, key]) => [ + [id, key, 'CR', '\r'], [id, key, 'LF', '\n'], + ]))('%s rejects %s credential header injection (%s) before HTTP', (id, key, _name, control) => { + const value = `synthetic${control}injected-header`; + const result = execute(workflow.jobs[id].steps[0].run, key, value); + expect(result.status).toBe(1); + expect(result.requests).toEqual([]); + expect(result.stderr).toContain('Credential contains invalid line breaks.'); + expect(result.stdout + result.stderr).not.toContain(value); + }); + + unixIt.each(fixtures)('%s propagates the curl failure without retrying or claiming success', (id, key) => { + const result = execute(workflow.jobs[id].steps[0].run, key, 'synthetic-failure-value', 22); + expect(result.status).toBe(22); + expect(result.requests).toHaveLength(1); + }); +});