fix(canvas): make Settings→Secrets reveal honest (value is write-only) #1421

Merged
devops-engineer merged 1 commits from fix/secrets-reveal-anthropic-internal into staging 2026-05-17 17:32:31 +00:00
Owner

What

Removes the dead reveal/eye control from SecretRow (canvas Settings → Secrets) and replaces it with an honest static write-only indicator.

Why

User reported the eye on CLAUDE_CODE_OAUTH_TOKEN "doesnt work" (ws 30ba7f0b-…). Root cause (observed in source, not Anthropic-specific, not a server 4xx/5xx):

  • SecretRow.tsx RevealToggle toggled a local revealed bool; the row always rendered secret.masked_value and never consumed revealed.
  • RevealToggle.tsx:26 shows eye-with-slash when revealed===true → a clicked row looks "active" but reveals nothing.
  • secrets.ts has no plaintext-fetch fn; server secrets.go:29 List "Never exposes values"; only decrypted route is bulk GET /secrets/values, token-gated for remote agents, never called by canvas.

Secret values are write-only from the browser by design. Reveal is architecturally impossible without a deliberate security regression (out of scope). The fix makes the affordance honest instead of lying. Edit (rotate/replace) and Delete are independent of reveal and unaffected.

Changes

  • canvas/src/components/settings/SecretRow.tsx: drop RevealToggle import/usage + dead revealed state + 30s auto-hide effect + reset-on-unmount effect; add static 🔒 write-only indicator (role="img", aria-label, title "Value is write-only and cannot be revealed — use Edit to replace/rotate it"). RevealToggle.tsx left intact — still used by KeyValueField.
  • SecretRow.test.tsx: replace "has Reveal…" assertion; add regression tests — no reveal toggle, write-only indicator present (incl. the CLAUDE_CODE_OAUTH_TOKEN/anthropic row), indicator is non-button + explanatory.

Test

  • npx vitest run src/components/settings/__tests__/SecretRow.test.tsx → 24 passed (3 new fail before / pass after).
  • Full src/components/settings + a11y suites: 208 passed. Lint clean for changed files.

Refs

  • Tracking: internal#490
  • Sibling Secrets/Tokens fixes: PR #1415 (fix/workspace-tokens-global-sentinel-500) + PR #1420 (fix(runtime+canvas): surface actionable provider errors) — referenced in triage as internal#210 / internal#211 (issue-title vs triage-framing mismatch noted in #490).
  • Scope guard: does not touch claude_sdk_executor.py / executor_helpers.py / agent-error path (internal#212).

Out of scope (noted for follow-up)

handleCopy also copies the masked placeholder (secrets.ts secrets.go) — a separate latent dead-affordance; not expanded here to keep the diff surgical to the reported reveal bug.

🤖 Generated with Claude Code


SOP-Checklist

Filled by the PR author (hongming). Each item answered truthfully from the
actual diff (canvas/src/components/settings/SecretRow.tsx +
SecretRow.test.tsx, 2 files, +61/-25, pure frontend) and the real
verification state. Peer-acks are a separate non-author reviewer action.

  • Comprehensive testing performed: Yes. The change is frontend-only
    (one React component + its vitest/RTL spec). The appropriate comprehensive
    test surface for it is the canvas vitest suite, which CI / all-required
    ran (Canvas (Next.js) job → npx vitest run --coverage, fired because
    the diff touches canvas/**) and which is green on head
    0d6b61bf. SecretRow.test.tsx gained 3 regression tests: (a) no
    reveal-toggle/"toggle reveal" control renders, (b) the static
    write-only-indicator is present with an honest title
    (/write-only|cannot be revealed/) and is not a <button>, and
    (c) the same indicator is present for the originally-reported
    CLAUDE_CODE_OAUTH_TOKEN (anthropic group) row; the prior
    "has Reveal, Copy, Edit, Delete" assertion was updated to drop Reveal.
    Verification is the authoritative CI run, not a local re-derivation.

  • Local-postgres E2E run: N/A — justified. Pure-frontend change.
    The diff is two .tsx files under canvas/src/components/settings/;
    it touches no Go handler, no SQL, no migration, no API route, no store
    persistence. There is no Postgres-reachable code path in this PR, so a
    local-postgres E2E would exercise nothing related to the change. The
    SOP config explicitly sanctions "N/A: pure-frontend change" for this
    item.

  • Staging-smoke verified or pending: Pending post-merge (no
    pre-merge backend surface to smoke) — justified. There is no server or
    API behaviour change to smoke; the only runtime delta is a static DOM
    element swapped for a dead interactive one. The canvas image redeploys
    to staging post-merge via the existing redeploy-tenants-on-staging
    pipeline; visual confirmation of the honest indicator is a post-merge
    staging check, not a pre-merge gate for a pure component change.

  • Root-cause not symptom: Root cause = the reveal/eye control was
    architecturally incapable of ever revealing a value, not a transient
    or environment-specific failure. Secret values are write-only from the
    browser by design: server secrets.go List "Never exposes values",
    there is no per-secret decrypt endpoint, the only decrypted path
    (GET /secrets/values) is bulk + token-gated for remote agents and is
    never called by canvas, and the client secrets.ts has no
    plaintext-fetch function at all. The old RevealToggle only flipped
    its own icon (to eye-with-slash) while the row always rendered
    masked_value, so it read as "broken" — the user-reported symptom on
    CLAUDE_CODE_OAUTH_TOKEN. The fix removes the lying affordance and
    states the write-only invariant honestly; it does not add a reveal
    capability (that would be a deliberate security regression — out of
    scope). This addresses the cause (a dead affordance contradicting the
    security model), not the symptom (one row "not working").

  • Five-Axis review walked: Yes. Genuine non-author dual review on
    head 0d6b61bf: core-fe APPROVE (review 4388, official) and
    core-uiux five-axis security/UX COMMENT (review 4409) — both record
    "CODE IS CLEAN, no finding" across correctness / security /
    tests / maintainability / ops. Security axis: the new element is a
    non-interactive role="img" span, not a button — it cannot fetch or
    display plaintext; attack surface is strictly reduced (a dead
    reveal-shaped control removed).

  • No backwards-compat shim / dead code added: Yes — none added.
    This is a net code reduction (removes revealed state, AUTO_HIDE_MS,
    the 30s auto-hide effect, and the reset-on-unmount effect). No compat
    shim is appropriate or added: secret values were never browser-readable,
    so there is no prior working behaviour to preserve a shim for — keeping
    a non-functional toggle "for compatibility" is exactly the
    dead-code-accretion this item guards against. RevealToggle.tsx is
    intentionally left in the tree because KeyValueField.tsx:74 still
    legitimately imports and renders it — verified; it is NOT orphaned dead
    code. The removal of the affordance from SecretRow is intentional and
    final.

  • Memory/saved-feedback consulted: Yes. Applicable saved feedback:
    feedback_surface_actionable_failure_reason_to_user (a control that
    silently does nothing is an opaque-failure defect — make the affordance
    honest about why the value can't be shown) and the
    fix-root-not-symptom principle (fix the architectural cause, not the
    single reported row). No memory contradicts this change; no
    canvas-accessibility memory applies adversely (the new element carries
    an aria-label + role="img").

## What Removes the dead reveal/eye control from `SecretRow` (canvas Settings → Secrets) and replaces it with an honest static **write-only indicator**. ## Why User reported the eye on `CLAUDE_CODE_OAUTH_TOKEN` "doesnt work" (ws `30ba7f0b-…`). Root cause (observed in source, not Anthropic-specific, not a server 4xx/5xx): - `SecretRow.tsx` `RevealToggle` toggled a local `revealed` bool; the row **always** rendered `secret.masked_value` and never consumed `revealed`. - `RevealToggle.tsx:26` shows **eye-with-slash** when `revealed===true` → a clicked row looks "active" but reveals nothing. - `secrets.ts` has **no** plaintext-fetch fn; server `secrets.go:29` `List` "Never exposes values"; only decrypted route is bulk `GET /secrets/values`, token-gated for remote agents, never called by canvas. Secret values are **write-only from the browser by design**. Reveal is architecturally impossible without a deliberate security regression (out of scope). The fix makes the affordance honest instead of lying. Edit (rotate/replace) and Delete are independent of reveal and unaffected. ## Changes - `canvas/src/components/settings/SecretRow.tsx`: drop `RevealToggle` import/usage + dead `revealed` state + 30s auto-hide effect + reset-on-unmount effect; add static `🔒` write-only indicator (`role="img"`, aria-label, title "Value is write-only and cannot be revealed — use Edit to replace/rotate it"). `RevealToggle.tsx` left intact — still used by `KeyValueField`. - `SecretRow.test.tsx`: replace "has Reveal…" assertion; add regression tests — no reveal toggle, write-only indicator present (incl. the `CLAUDE_CODE_OAUTH_TOKEN`/anthropic row), indicator is non-button + explanatory. ## Test - `npx vitest run src/components/settings/__tests__/SecretRow.test.tsx` → 24 passed (3 new fail before / pass after). - Full `src/components/settings` + a11y suites: 208 passed. Lint clean for changed files. ## Refs - Tracking: internal#490 - Sibling Secrets/Tokens fixes: PR #1415 (`fix/workspace-tokens-global-sentinel-500`) + PR #1420 (`fix(runtime+canvas): surface actionable provider errors`) — referenced in triage as internal#210 / internal#211 (issue-title vs triage-framing mismatch noted in #490). - Scope guard: does not touch `claude_sdk_executor.py` / `executor_helpers.py` / agent-error path (internal#212). ## Out of scope (noted for follow-up) `handleCopy` also copies the masked placeholder (`secrets.ts` `secrets.go`) — a separate latent dead-affordance; not expanded here to keep the diff surgical to the reported reveal bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- ## SOP-Checklist Filled by the PR author (hongming). Each item answered truthfully from the actual diff (`canvas/src/components/settings/SecretRow.tsx` + `SecretRow.test.tsx`, 2 files, +61/-25, pure frontend) and the real verification state. Peer-acks are a separate non-author reviewer action. - [x] **Comprehensive testing performed**: Yes. The change is frontend-only (one React component + its vitest/RTL spec). The appropriate comprehensive test surface for it is the canvas vitest suite, which `CI / all-required` ran (`Canvas (Next.js)` job → `npx vitest run --coverage`, fired because the diff touches `canvas/**`) and which is **green** on head `0d6b61bf`. `SecretRow.test.tsx` gained 3 regression tests: (a) no `reveal-toggle`/"toggle reveal" control renders, (b) the static `write-only-indicator` is present with an honest `title` (`/write-only|cannot be revealed/`) and is **not** a `<button>`, and (c) the same indicator is present for the originally-reported `CLAUDE_CODE_OAUTH_TOKEN` (anthropic group) row; the prior "has Reveal, Copy, Edit, Delete" assertion was updated to drop Reveal. Verification is the authoritative CI run, not a local re-derivation. - [x] **Local-postgres E2E run**: N/A — justified. Pure-frontend change. The diff is two `.tsx` files under `canvas/src/components/settings/`; it touches no Go handler, no SQL, no migration, no API route, no store persistence. There is no Postgres-reachable code path in this PR, so a local-postgres E2E would exercise nothing related to the change. The SOP config explicitly sanctions `"N/A: pure-frontend change"` for this item. - [x] **Staging-smoke verified or pending**: Pending post-merge (no pre-merge backend surface to smoke) — justified. There is no server or API behaviour change to smoke; the only runtime delta is a static DOM element swapped for a dead interactive one. The canvas image redeploys to staging post-merge via the existing `redeploy-tenants-on-staging` pipeline; visual confirmation of the honest indicator is a post-merge staging check, not a pre-merge gate for a pure component change. - [x] **Root-cause not symptom**: Root cause = the reveal/eye control was *architecturally incapable* of ever revealing a value, not a transient or environment-specific failure. Secret values are write-only from the browser by design: server `secrets.go` `List` "Never exposes values", there is no per-secret decrypt endpoint, the only decrypted path (`GET /secrets/values`) is bulk + token-gated for remote agents and is never called by canvas, and the client `secrets.ts` has no plaintext-fetch function at all. The old `RevealToggle` only flipped its own icon (to eye-with-slash) while the row always rendered `masked_value`, so it read as "broken" — the user-reported symptom on `CLAUDE_CODE_OAUTH_TOKEN`. The fix removes the lying affordance and states the write-only invariant honestly; it does not add a reveal capability (that would be a deliberate security regression — out of scope). This addresses the cause (a dead affordance contradicting the security model), not the symptom (one row "not working"). - [x] **Five-Axis review walked**: Yes. Genuine non-author dual review on head `0d6b61bf`: `core-fe` APPROVE (review 4388, official) and `core-uiux` five-axis security/UX COMMENT (review 4409) — both record "CODE IS CLEAN, no finding" across correctness / security / tests / maintainability / ops. Security axis: the new element is a non-interactive `role="img"` span, not a button — it cannot fetch or display plaintext; attack surface is strictly reduced (a dead reveal-shaped control removed). - [x] **No backwards-compat shim / dead code added**: Yes — none added. This is a net code reduction (removes `revealed` state, `AUTO_HIDE_MS`, the 30s auto-hide effect, and the reset-on-unmount effect). No compat shim is appropriate or added: secret values were never browser-readable, so there is no prior working behaviour to preserve a shim for — keeping a non-functional toggle "for compatibility" is exactly the dead-code-accretion this item guards against. `RevealToggle.tsx` is intentionally left in the tree because `KeyValueField.tsx:74` still legitimately imports and renders it — verified; it is NOT orphaned dead code. The removal of the affordance from `SecretRow` is intentional and final. - [x] **Memory/saved-feedback consulted**: Yes. Applicable saved feedback: `feedback_surface_actionable_failure_reason_to_user` (a control that silently does nothing is an opaque-failure defect — make the affordance honest about *why* the value can't be shown) and the fix-root-not-symptom principle (fix the architectural cause, not the single reported row). No memory contradicts this change; no canvas-accessibility memory applies adversely (the new element carries an `aria-label` + `role="img"`).
hongming added 1 commit 2026-05-17 14:25:49 +00:00
fix(canvas): make Settings→Secrets reveal honest (value is write-only)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 3s
CI / Detect changes (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 12s
E2E Chat / detect-changes (pull_request) Successful in 14s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
Harness Replays / detect-changes (pull_request) Successful in 6s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
qa-review / approved (pull_request) Successful in 8s
security-review / approved (pull_request) Successful in 8s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m4s
CI / Platform (Go) (pull_request) Successful in 8m18s
CI / Canvas (Next.js) (pull_request) Successful in 9m14s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Python Lint & Test (pull_request) Successful in 1s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 1s
E2E Chat / E2E Chat (pull_request) Failing after 9s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 1s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 4s
gate-check-v3 / gate-check (pull_request) Successful in 3s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-tier-check / tier-check (pull_request) Successful in 6s
audit-force-merge / audit (pull_request) Successful in 5s
0d6b61bfff
The eye/RevealToggle in SecretRow was a dead affordance: it flipped a
local `revealed` boolean but the row always rendered `masked_value` and
never consumed it, so nothing was ever revealed. RevealToggle renders an
eye-WITH-SLASH when revealed=true, so a clicked row looked "active" while
showing nothing — read by users as "this doesnt work" (reported on
CLAUDE_CODE_OAUTH_TOKEN / Anthropic group).

Root cause is not Anthropic/OAuth/category-specific and not a server
4xx/5xx: secret values are write-only from the browser by design — the
server List handler "Never exposes values", there is no per-secret
decrypt route, and the only decrypted path (GET /secrets/values) is bulk
+ token-gated for remote agents and never called by canvas. The client
has no plaintext-fetch function. Reveal is architecturally impossible
without a deliberate security regression (out of scope).

Fix: remove the dead toggle (+ its local state / auto-hide effect) and
show a static write-only indicator (lock + explanatory title). Edit
(rotate/replace) and Delete are unaffected and independent of reveal.

Refs: internal#490; sibling Secrets/Tokens fixes PR #1415 + #1420
(referenced in triage as internal#210 / internal#211). Does not touch
the agent-error path (internal#212).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member

[core-security-agent] APPROVED — security-positive UX. SecretRow: removes broken RevealToggle (values are write-only, no server-side decrypt path exists from browser). Replaces with static lock emoji + aria-label + title describing write-only contract. aria-label is static, not user-controlled. OWASP 0/1

[core-security-agent] APPROVED — security-positive UX. SecretRow: removes broken RevealToggle (values are write-only, no server-side decrypt path exists from browser). Replaces with static lock emoji + aria-label + title describing write-only contract. aria-label is static, not user-controlled. OWASP 0/1
core-fe approved these changes 2026-05-17 14:35:13 +00:00
core-fe left a comment
Member

core-fe review

APPROVE — good UX fix.

What this changes

SecretRow.tsx removes the non-functional RevealToggle (eye icon) that appeared to reveal secret values but couldn't — the server's List endpoint never exposes values. Replaced with an honest static "write-only" indicator with role="img" and a descriptive aria-label explaining that the value cannot be revealed and Edit should be used instead.

Code quality

  • Clean removal of revealed state, revealTimerRef, and related timer cleanup logic — dead code gone
  • Descriptive comment explaining why values are write-only
  • aria-label on the write-only indicator is descriptive and actionable
  • Test coverage updated with write-only-indicator testid

UX note

Users clicking "eye" and seeing "not available" is worse than no button at all — this is the right call. The indicator honestly communicates what the system can do.

## core-fe review APPROVE — good UX fix. ### What this changes SecretRow.tsx removes the non-functional `RevealToggle` (eye icon) that appeared to reveal secret values but couldn't — the server's List endpoint never exposes values. Replaced with an honest static "write-only" indicator with `role="img"` and a descriptive `aria-label` explaining that the value cannot be revealed and Edit should be used instead. ### Code quality - Clean removal of `revealed` state, `revealTimerRef`, and related timer cleanup logic — dead code gone ✅ - Descriptive comment explaining why values are write-only ✅ - `aria-label` on the write-only indicator is descriptive and actionable ✅ - Test coverage updated with `write-only-indicator` testid ✅ ### UX note Users clicking "eye" and seeing "not available" is worse than no button at all — this is the right call. The indicator honestly communicates what the system can do.
Member

/sop-ack comprehensive-testing Canvas Vitest + SecretRow.test.tsx updated with write-only-indicator coverage. 210+ test files pass.

/sop-ack comprehensive-testing Canvas Vitest + SecretRow.test.tsx updated with write-only-indicator coverage. 210+ test files pass.
Member

/sop-ack five-axis-review UX fix: removes non-functional reveal toggle, replaces with honest write-only indicator. Clean removal of dead state + timer logic. aria-label on indicator is descriptive.

/sop-ack five-axis-review UX fix: removes non-functional reveal toggle, replaces with honest write-only indicator. Clean removal of dead state + timer logic. aria-label on indicator is descriptive.
Member

/sop-ack memory-consulted PLAN.md — no canvas accessibility issues in memory related to this change.

/sop-ack memory-consulted PLAN.md — no canvas accessibility issues in memory related to this change.
Member

/sop-ack no-backwards-compat UX change — secrets tab reveal behavior changes (no longer shows broken eye icon). No API surface change.

/sop-ack no-backwards-compat UX change — secrets tab reveal behavior changes (no longer shows broken eye icon). No API surface change.
Member

/sop-ack local-postgres-e2e Canvas Vitest 210 files, 3293 tests pass. Pure canvas UI change.

/sop-ack local-postgres-e2e Canvas Vitest 210 files, 3293 tests pass. Pure canvas UI change.
Member

/sop-ack staging-smoke Canvas Vitest 210 files, 3293 tests pass. Pure canvas UI change — no backend surface.

/sop-ack staging-smoke Canvas Vitest 210 files, 3293 tests pass. Pure canvas UI change — no backend surface.
Member

[core-qa-agent] N/A — Canvas-only: SecretRow reveal fix (write-only value) + SecretRow.test.tsx coverage. Pure Canvas UI.

[core-qa-agent] N/A — Canvas-only: SecretRow reveal fix (write-only value) + SecretRow.test.tsx coverage. Pure Canvas UI.
core-uiux reviewed 2026-05-17 17:00:43 +00:00
core-uiux left a comment
Member

Genuine five-axis review (UX/security focus) — CODE IS CLEAN, no finding:

  • Security: removes a dead reveal affordance (server List never exposes values, no decrypt route); new element is a non-interactive role=img span, not a button — cannot fetch/show plaintext. No secret-reveal regression; attack surface strictly reduced.
  • Correctness: revealed state + auto-hide timer + cleanup effect removed cleanly, no dangling refs; masked_value still rendered.
  • Tests: 3 regression tests incl. the reported CLAUDE_CODE_OAUTH_TOKEN row.
  • Maintainability/Ops: clear rationale comment, net simplification.

NOT approving / cannot merge: the REQUIRED context 'sop-checklist / all-items-acked' is FAILING on the current head (acked 5/7 — missing: root-cause, no-backwards-compat; body-unfilled: comprehensive-testing, local-postgres-e2e, staging-smoke, +4). Author must fill the SOP checklist sections + acks in the PR body. PARKED on that required gate — no bypass, no compensating status.

Genuine five-axis review (UX/security focus) — CODE IS CLEAN, no finding: - Security: removes a dead reveal affordance (server List never exposes values, no decrypt route); new element is a non-interactive role=img span, not a button — cannot fetch/show plaintext. No secret-reveal regression; attack surface strictly reduced. - Correctness: revealed state + auto-hide timer + cleanup effect removed cleanly, no dangling refs; masked_value still rendered. - Tests: 3 regression tests incl. the reported CLAUDE_CODE_OAUTH_TOKEN row. - Maintainability/Ops: clear rationale comment, net simplification. NOT approving / cannot merge: the REQUIRED context 'sop-checklist / all-items-acked' is FAILING on the current head (acked 5/7 — missing: root-cause, no-backwards-compat; body-unfilled: comprehensive-testing, local-postgres-e2e, staging-smoke, +4). Author must fill the SOP checklist sections + acks in the PR body. PARKED on that required gate — no bypass, no compensating status.
app-lead approved these changes 2026-05-17 17:16:24 +00:00
app-lead left a comment
Member

Senior review (app-lead, managers team) — PR #1421 @ 0d6b61bf

Genuine five-axis senior review. Verdict: CLEAN — no Critical/Required findings.

Root-cause vs symptom (sop-ack gated)

The reported defect singled out CLAUDE_CODE_OAUTH_TOKEN not revealing. The symptom-only fix would have wired a decrypt call for that one row/group. The real root-cause, evidenced in code: secret values are architecturally write-only from the browser — server List endpoint "Never exposes values", there is no per-secret decrypt route, and SecretRow only ever holds secret.masked_value (no plaintext field on the Secret type). The old RevealToggle was driven by local useState(false) and could only flip its own eye icon — it could never produce plaintext. Removing it for all rows (group-agnostic; covered by the OAuth-token regression test) and replacing with an honest static indicator is the true root-cause fix, not papering over the one reported row. PASSES.

No backwards-compat shim / dead code (sop-ack gated)

No compat shim added — correct: values were never browser-readable so there is nothing to preserve and no consumer behavior regressed. The change removes dead code (revealed state, AUTO_HIDE_MS, two useEffect hooks, revealTimerRef). RevealToggle.tsx is verified NOT orphaned: KeyValueField.tsx still imports (line 4) and uses (line 74) it for a user-entered input where type={revealed ? 'text' : 'password'} — reveal is genuinely meaningful on that distinct surface. No dangling revealed/RevealToggle symbols remain in SecretRow; useEffect is still used by the validation debounce. No dead code accreted. PASSES.

Correctness / readability / architecture

Clean removal, well-documented rationale comment. Indicator is role=img + aria-label + non-button element — honest, non-interactive. Architecturally consistent with the write-only secrets model.

Security

Strictly reduces attack surface (removes a state path). Only masked_value is ever rendered/copied (pre-existing, untouched by this PR). No plaintext exposure introduced or regressed. Net security-positive.

Performance

Removes two useEffect hooks + a timer per secret row — net positive at scale.

Tests

3 new regression tests genuinely cover: (a) reveal toggle absent, (b) write-only indicator present + honest (asserts non-BUTTON tag and title text), (c) the specific reported CLAUDE_CODE_OAUTH_TOKEN row. Real behavioral coverage of the no-reveal / honest-indicator contract.

Approving on the senior-attestation axes. /sop-ack for root-cause and no-backwards-compat posted separately as issue comments.

## Senior review (app-lead, managers team) — PR #1421 @ 0d6b61bf **Genuine five-axis senior review. Verdict: CLEAN — no Critical/Required findings.** ### Root-cause vs symptom (sop-ack gated) The reported defect singled out `CLAUDE_CODE_OAUTH_TOKEN` not revealing. The symptom-only fix would have wired a decrypt call for that one row/group. The real root-cause, evidenced in code: secret values are **architecturally write-only from the browser** — server List endpoint "Never exposes values", there is no per-secret decrypt route, and `SecretRow` only ever holds `secret.masked_value` (no plaintext field on the `Secret` type). The old `RevealToggle` was driven by local `useState(false)` and could only flip its own eye icon — it could never produce plaintext. Removing it for **all** rows (group-agnostic; covered by the OAuth-token regression test) and replacing with an honest static indicator is the true root-cause fix, not papering over the one reported row. **PASSES.** ### No backwards-compat shim / dead code (sop-ack gated) No compat shim added — correct: values were never browser-readable so there is nothing to preserve and no consumer behavior regressed. The change *removes* dead code (`revealed` state, `AUTO_HIDE_MS`, two `useEffect` hooks, `revealTimerRef`). `RevealToggle.tsx` is verified NOT orphaned: `KeyValueField.tsx` still imports (line 4) and uses (line 74) it for a user-entered input where `type={revealed ? 'text' : 'password'}` — reveal is genuinely meaningful on that distinct surface. No dangling `revealed`/`RevealToggle` symbols remain in `SecretRow`; `useEffect` is still used by the validation debounce. No dead code accreted. **PASSES.** ### Correctness / readability / architecture Clean removal, well-documented rationale comment. Indicator is `role=img` + `aria-label` + non-button element — honest, non-interactive. Architecturally consistent with the write-only secrets model. ### Security Strictly reduces attack surface (removes a state path). Only `masked_value` is ever rendered/copied (pre-existing, untouched by this PR). No plaintext exposure introduced or regressed. Net security-positive. ### Performance Removes two `useEffect` hooks + a timer per secret row — net positive at scale. ### Tests 3 new regression tests genuinely cover: (a) reveal toggle absent, (b) write-only indicator present + honest (asserts non-BUTTON tag and title text), (c) the specific reported `CLAUDE_CODE_OAUTH_TOKEN` row. Real behavioral coverage of the no-reveal / honest-indicator contract. Approving on the senior-attestation axes. /sop-ack for root-cause and no-backwards-compat posted separately as issue comments.
Member

/sop-ack root-cause Root-cause is the write-only-from-browser architecture: no per-secret decrypt route, SecretRow only ever holds masked_value, the RevealToggle was local-state-only and could never produce plaintext. Removing the lying affordance for ALL rows (group-agnostic, covered by the OAuth-token regression test) is the true fix, not a per-row symptom patch. Senior-attested after full code review (see app-lead review 4411).

/sop-ack root-cause Root-cause is the write-only-from-browser architecture: no per-secret decrypt route, SecretRow only ever holds masked_value, the RevealToggle was local-state-only and could never produce plaintext. Removing the lying affordance for ALL rows (group-agnostic, covered by the OAuth-token regression test) is the true fix, not a per-row symptom patch. Senior-attested after full code review (see app-lead review 4411).
Member

/sop-ack no-backwards-compat No compat shim is correct — values were never browser-readable so nothing to preserve and no consumer regressed. Change removes dead code (revealed state, AUTO_HIDE_MS, 2 useEffects, timer ref). RevealToggle.tsx verified still used by KeyValueField.tsx (import line 4, usage line 74) on a legitimately-revealable user-input surface, so it is NOT orphaned. No dead code accreted. Senior-attested after full code review (see app-lead review 4411).

/sop-ack no-backwards-compat No compat shim is correct — values were never browser-readable so nothing to preserve and no consumer regressed. Change removes dead code (revealed state, AUTO_HIDE_MS, 2 useEffects, timer ref). RevealToggle.tsx verified still used by KeyValueField.tsx (import line 4, usage line 74) on a legitimately-revealable user-input surface, so it is NOT orphaned. No dead code accreted. Senior-attested after full code review (see app-lead review 4411).
devops-engineer merged commit 283ebd5b47 into staging 2026-05-17 17:32:31 +00:00
devops-engineer deleted branch fix/secrets-reveal-anthropic-internal 2026-05-17 17:32:32 +00:00
Sign in to join this conversation.
6 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1421