Skip to content

Add GitHub secrets provider#718

Open
phucnguyen1707 wants to merge 1 commit into
profullstack:masterfrom
phucnguyen1707:add-github-secrets-provider
Open

Add GitHub secrets provider#718
phucnguyen1707 wants to merge 1 commit into
profullstack:masterfrom
phucnguyen1707:add-github-secrets-provider

Conversation

@phucnguyen1707

Copy link
Copy Markdown

Adds a GitHub Secrets provider under packages/secrets/github for the environment-updater work in #710. It supports listing secret metadata with gh secret list and pushing repository, environment, organization, or user scoped secrets with gh secret set without logging secret values.\n\nAlso registers the provider in the CLI adapter registry and adds tests for list parsing, repository environment pushes, and organization visibility args.\n\nChecks run:\n- pnpm --filter @profullstack/sh1pt-core build\n- pnpm vitest run packages/secrets/github/src/index.test.ts\n- pnpm --filter @profullstack/sh1pt-secrets-github typecheck\n- pnpm --filter @profullstack/sh1pt typecheck

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a new @profullstack/sh1pt-secrets-github package that wraps the gh CLI to list and push GitHub Actions secrets across repository, environment, organization, and user scopes, and registers it in the CLI adapter registry.

  • packages/secrets/github/src/index.ts — implements pull (via gh secret list --json), push (via gh secret set --body with the value redacted in logs), and connect; helper functions handle scope args and key validation.
  • packages/secrets/github/src/index.test.ts — covers list-output parsing, repo-environment push sequencing, and org visibility arg construction via a mocked exec.
  • packages/cli/src/adapter-registry.ts — adds 'github' to the secrets adapter list alongside Doppler, dotenvx, and 1Password.

Confidence Score: 3/5

The new provider is mostly well-structured but has a JSON parsing gap that can produce confusing runtime failures, and two edge-case issues in the arg-building helpers.

The unguarded JSON.parse in parseSecretList will throw an opaque SyntaxError whenever gh emits any non-JSON text on stdout — a realistic scenario when the CLI prints deprecation notices or auth warnings. This will silently break pull for affected users with no actionable error message. The orgVisibilityArgs bug and the no-op connect are narrower concerns but still represent wrong behavior on reachable code paths.

packages/secrets/github/src/index.ts deserves a close look — specifically the parseSecretList, orgVisibilityArgs, and connect functions.

Important Files Changed

Filename Overview
packages/secrets/github/src/index.ts Core provider implementation — has an unguarded JSON.parse in parseSecretList (P1), an incorrect --no-repos-selected path for user-scoped secrets (P2), and a connect() that logs but never executes gh auth status (P2).
packages/secrets/github/src/index.test.ts Tests cover list parsing, repo/environment push, and org visibility args; no test for invalid JSON output or the user+noReposSelected combination.
packages/cli/src/adapter-registry.ts Registers the new 'github' adapter in the secrets category; change is minimal and correct.
packages/secrets/github/package.json New package manifest following existing workspace conventions; exports and scripts look correct.
packages/secrets/github/tsconfig.json Extends the shared tsconfig.base.json with correct outDir/rootDir; no issues.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant GitHubProvider
    participant exec
    participant gh as gh CLI

    Note over Caller,gh: connect()
    Caller->>GitHubProvider: connect(ctx, config)
    GitHubProvider-->>Caller: "{ accountId } (no subprocess run)"

    Note over Caller,gh: pull()
    Caller->>GitHubProvider: pull(ctx, config)
    GitHubProvider->>exec: gh secret list --app actions --json ... [--repo/--org/--env/--user]
    exec->>gh: spawn process
    gh-->>exec: stdout (JSON array)
    exec-->>GitHubProvider: "{ stdout }"
    GitHubProvider->>GitHubProvider: parseSecretList(stdout) → JSON.parse (unguarded)
    GitHubProvider-->>Caller: SecretRef[]

    Note over Caller,gh: push()
    Caller->>GitHubProvider: push(ctx, secrets, config)
    loop for each secret
        GitHubProvider->>GitHubProvider: assertSecretKey(key)
        GitHubProvider->>GitHubProvider: resolve value (secret.value ?? ctx.secret(key))
        GitHubProvider->>exec: gh secret set --app actions [--repo/--org/--env] KEY --body VALUE
        exec->>gh: spawn process
        gh-->>exec: exit code
        exec-->>GitHubProvider: result
    end
    GitHubProvider-->>Caller: "{ count }"
Loading

Reviews (1): Last reviewed commit: "Add GitHub secrets provider" | Re-trigger Greptile

Comment on lines +64 to +76
function parseSecretList(stdout: string): SecretRef[] {
const body = stdout.trim();
if (!body) return [];
const entries = JSON.parse(body) as GitHubSecretListEntry[];
return entries.map((entry) => ({
key: entry.name,
path: [
entry.visibility,
entry.numSelectedRepos !== undefined ? `${entry.numSelectedRepos} selected repos` : undefined,
entry.updatedAt,
].filter(Boolean).join(' · ') || undefined,
}));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unguarded JSON.parse will surface as an opaque SyntaxError

parseSecretList calls JSON.parse(body) with no try/catch. If gh secret list --json returns a non-zero exit code that somehow slips through, emits a warning/notice on stdout before the JSON array, or produces truncated output, the caller receives a raw SyntaxError with no indication of what the gh output actually contained. Wrapping the parse in a try/catch and rethrowing with the raw stdout snippet in the message would make diagnosis far easier in production.

Comment on lines +48 to +54
function orgVisibilityArgs(config: Config): string[] {
if (!text(config.org) && !config.user) return [];
if (config.noReposSelected) return ['--no-repos-selected'];
if (config.repos?.length) return ['--repos', config.repos.join(',')];
if (config.visibility) return ['--visibility', config.visibility];
return [];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 --no-repos-selected incorrectly allowed for user-scoped secrets

orgVisibilityArgs gates only on !org && !user, so when config.user = true and config.noReposSelected = true the function returns ['--no-repos-selected']. The GitHub CLI only accepts --no-repos-selected on org secrets; passing it on a user-scoped call will cause gh to error. A guard like if (!text(config.org)) return [] before the noReposSelected branch would prevent this invalid combination from reaching the CLI.

Comment on lines +82 to +86
async connect(ctx, config) {
const scope = text(config.repo) ?? text(config.org) ?? (config.user ? 'user' : 'current repository');
ctx.log(`gh auth status · scope=${scope}`);
return { accountId: scope };
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 connect logs but never runs gh auth status

The log line gh auth status · scope=… implies the command is executed, but the method only constructs a string and returns without running any subprocess. Authentication failures therefore aren't surfaced at connect time — the first real error appears at pull or push, typically with a less helpful message. Running exec('gh', ['auth', 'status'], { throwOnNonZero: true }) here would give callers an early, actionable failure.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant