Skip to content

feat: make sensitive fields secret-picker-only#20

Merged
TerrifiedBug merged 3 commits intomainfrom
feat/secret-only-sensitive-fields
Mar 6, 2026
Merged

feat: make sensitive fields secret-picker-only#20
TerrifiedBug merged 3 commits intomainfrom
feat/secret-only-sensitive-fields

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

  • Remove plaintext password input from SecretPickerInput — sensitive fields can only be set via SECRET[] references
  • Add three render states: secret reference badge, legacy plaintext warning, and empty picker button
  • Add inline "Create Secret" form in the picker popover for creating secrets without leaving the config form
  • Add autoComplete="off" on secret value input to prevent browser password manager capture
  • Show guidance message when legacy plaintext value exists but no environment is selected

Test Plan

  • Open a pipeline with a sink that has sensitive fields (e.g., Elasticsearch with auth)
  • Verify the password field shows "Select secret..." button (not a text input)
  • Click it — verify the popover shows secrets list and "Create new secret" button
  • Click "Create new secret" — verify the inline form appears
  • Create a secret — verify it auto-selects and shows the badge
  • Clear it — verify it goes back to "Select secret..." state
  • Set a plaintext value in the DB manually — verify it shows the destructive warning badge

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR removes the plaintext password <Input> from SecretPickerInput and enforces that sensitive fields can only be configured via SECRET[name] references. It introduces three distinct render states (active reference badge, legacy plaintext warning, and empty picker), and adds an inline "Create Secret" form inside the picker popover — letting users create a secret without leaving the config form.

Key changes:

  • placeholder prop removed from SecretPickerInput (no longer a text input, so not applicable)
  • isPlaintextLegacy detection surfaces a destructive warning badge for any existing plaintext DB values, with a clear button and a guided path to replace with a secret reference
  • Inline secret creation uses useMutationtrpc.secret.create with immediate cache invalidation and auto-selection of the new reference
  • autoComplete="new-password" correctly signals to browsers that this is a new credential entry (not a login form), suppressing saved-credential autofill
  • The shared pickerPopover JSX variable cleanly avoids duplicating popover markup across the two non-badge states

Confidence Score: 4/5

  • Safe to merge — well-structured security improvement with two minor style issues that don't affect correctness.
  • The changes are a clear security improvement: removing plaintext password input eliminates a whole class of accidental credential exposure in pipeline configs. The inline create flow is correctly wired (mutation → invalidation → auto-select). The two flagged items (value trimming and loading-state flash) are non-breaking style concerns. No tRPC authorization or audit middleware changes are involved since this is purely a frontend component.
  • src/components/config-forms/secret-picker-input.tsx — review the newValue.trim() call and the loading-state condition order in the secrets list.

Important Files Changed

Filename Overview
src/components/config-forms/secret-picker-input.tsx Major rework to enforce secret-reference-only input for sensitive fields. Adds three render states (badge, legacy plaintext warning, empty picker), inline "Create Secret" form with mutation + cache invalidation, and proper autoComplete="new-password". Two minor style concerns: silent trim() on secret values, and a brief "No secrets yet" flash on popover open due to loading-state check order.
src/components/config-forms/field-renderer.tsx Trivial one-line removal of the placeholder prop from the SecretPickerInput call site, consistent with the prop being removed from the component's interface.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SecretPickerInput renders] --> B{value is SECRET ref?}
    B -- Yes --> C[State 1: Badge + Clear button]
    B -- No --> D{value is non-empty plaintext?}
    D -- Yes --> E[State 2: Destructive warning badge + Clear button]
    D -- No --> F[State 3: Empty picker button]

    E --> G{environmentId set?}
    F --> G
    G -- No --> H[Fallback text message]
    G -- Yes --> I[Popover trigger rendered]

    I --> J{Popover opened?}
    J -- Yes --> K[Fetch secret.list for environment]
    K --> L{showCreate?}
    L -- No --> M[List existing secrets + Create new secret button]
    L -- Yes --> N[Inline create form: Name + Value inputs]

    M -- Select existing --> O[onChange: SECRET ref  •  close popover]
    M -- Click Create new secret --> L
    N -- Click Create & Use --> P[secret.create mutation]
    P -- onSuccess --> Q[Invalidate secret.list cache]
    Q --> R[onChange: new SECRET ref  •  close popover  •  toast]
    N -- Cancel --> M
Loading

Last reviewed commit: 67b725a

<Label className="text-xs">Value</Label>
<Input
type="password"
autoComplete="off"
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.

autoComplete="off" is ignored by modern browsers on password fields.

The PR description explicitly states this was added to prevent password manager capture, but Chrome, Firefox, and Safari intentionally ignore autoComplete="off" on type="password" inputs per the HTML spec, as a user-protection measure against being locked out of their own accounts.

autoComplete="new-password" is the widely-accepted signal that tells browsers this is a new secret being entered (not a login form), which suppresses autofill from saved credentials:

Suggested change
autoComplete="off"
type="password"
autoComplete="new-password"
value={newValue}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/config-forms/secret-picker-input.tsx
Line: 171

Comment:
`autoComplete="off"` is ignored by modern browsers on password fields.

The PR description explicitly states this was added to prevent password manager capture, but Chrome, Firefox, and Safari intentionally ignore `autoComplete="off"` on `type="password"` inputs per the HTML spec, as a user-protection measure against being locked out of their own accounts.

`autoComplete="new-password"` is the widely-accepted signal that tells browsers this is a *new* secret being entered (not a login form), which suppresses autofill from saved credentials:

```suggestion
                  type="password"
                  autoComplete="new-password"
                  value={newValue}
```

How can I resolve this? If you propose a fix, please make it concise.

Browsers commonly ignore autoComplete="off" on password fields.
"new-password" is more reliably respected to prevent autofill.
createMutation.mutate({
environmentId,
name: newName.trim(),
value: newValue.trim(),
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.

Silent whitespace stripping on secret value

newValue.trim() is called before sending the secret value to the server. While accidental leading/trailing whitespace is common in pasted API keys, there are secret formats (e.g. some base64-padded tokens or generated passwords) where the stored value must be byte-exact. Silently trimming means a user who pastes such a value will get a secret that doesn't work, with no indication of why.

The name field trimming (same line) is unambiguously correct — names should never have surrounding whitespace. The value trim is the debatable one.

Consider trimming name only, or at minimum alerting the user if trimming actually changed their input:

Suggested change
value: newValue.trim(),
name: newName.trim(),
value: newValue,
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/config-forms/secret-picker-input.tsx
Line: 77

Comment:
**Silent whitespace stripping on secret value**

`newValue.trim()` is called before sending the secret value to the server. While accidental leading/trailing whitespace is common in pasted API keys, there are secret formats (e.g. some base64-padded tokens or generated passwords) where the stored value must be byte-exact. Silently trimming means a user who pastes such a value will get a secret that doesn't work, with no indication of why.

The `name` field trimming (same line) is unambiguously correct — names should never have surrounding whitespace. The value trim is the debatable one.

Consider trimming `name` only, or at minimum alerting the user if trimming actually changed their input:

```suggestion
      name: newName.trim(),
      value: newValue,
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +130 to +137
{secrets.length === 0 && !secretsQuery.isLoading ? (
<p className="p-3 text-xs text-muted-foreground text-center">
No secrets yet
</p>
) : secretsQuery.isLoading ? (
<p className="p-3 text-xs text-muted-foreground text-center">
Loading...
</p>
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.

Brief "No secrets yet" flash on first open

The condition order can show "No secrets yet" for a frame before "Loading..." when the popover first opens. When popoverOpen flips to true, the query's enabled flag transitions from falsetrue. In the same render where PopoverContent appears, secretsQuery.isLoading may still be false (the fetch hasn't started yet), so secrets.length === 0 && !secretsQuery.isLoading evaluates to true and "No secrets yet" briefly flashes before the loading state kicks in.

A more robust guard is to also check whether the query is enabled but hasn't yet returned data (secretsQuery.isPending):

Suggested change
{secrets.length === 0 && !secretsQuery.isLoading ? (
<p className="p-3 text-xs text-muted-foreground text-center">
No secrets yet
</p>
) : secretsQuery.isLoading ? (
<p className="p-3 text-xs text-muted-foreground text-center">
Loading...
</p>
{secretsQuery.isPending ? (
<p className="p-3 text-xs text-muted-foreground text-center">
Loading...
</p>
) : secrets.length === 0 ? (
<p className="p-3 text-xs text-muted-foreground text-center">
No secrets yet
</p>
) : (
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/config-forms/secret-picker-input.tsx
Line: 130-137

Comment:
**Brief "No secrets yet" flash on first open**

The condition order can show "No secrets yet" for a frame before "Loading..." when the popover first opens. When `popoverOpen` flips to `true`, the query's `enabled` flag transitions from `false``true`. In the same render where `PopoverContent` appears, `secretsQuery.isLoading` may still be `false` (the fetch hasn't started yet), so `secrets.length === 0 && !secretsQuery.isLoading` evaluates to `true` and "No secrets yet" briefly flashes before the loading state kicks in.

A more robust guard is to also check whether the query is enabled but hasn't yet returned data (`secretsQuery.isPending`):

```suggestion
          {secretsQuery.isPending ? (
            <p className="p-3 text-xs text-muted-foreground text-center">
              Loading...
            </p>
          ) : secrets.length === 0 ? (
            <p className="p-3 text-xs text-muted-foreground text-center">
              No secrets yet
            </p>
          ) : (
```

How can I resolve this? If you propose a fix, please make it concise.

@TerrifiedBug TerrifiedBug merged commit 84943d9 into main Mar 6, 2026
10 checks passed
@TerrifiedBug TerrifiedBug deleted the feat/secret-only-sensitive-fields branch March 6, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant