Skip to content

fix: move OpenAI key to secret installation parameter [AIS-295] - #11202

Open
Tyler Pina (tylerpina) wants to merge 17 commits into
masterfrom
fix/openai-key-secret-param-ais-295
Open

fix: move OpenAI key to secret installation parameter [AIS-295]#11202
Tyler Pina (tylerpina) wants to merge 17 commits into
masterfrom
fix/openai-key-secret-param-ais-295

Conversation

@tylerpina

@tylerpina Tyler Pina (tylerpina) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

AIS-295

Summary

  • Adds a Contentful Function (functions/openai-proxy.ts) that reads the OpenAI API key server-side via context.appInstallationParameters and proxies chat completion requests to OpenAI — the key never touches browser-side code
  • Wires up a contentful-app-manifest.json with an App Action (openaiProxyAction) so the Dialog location calls the function via sdk.cma.appActionCall.createWithResponse instead of calling OpenAI directly
  • Removes key from AppInstallationParameters and all browser-readable state; config screen APIKey component becomes write-only (admin can set a new key but can't read the current one back); Model dropdown uses a hardcoded list instead of fetching via the key; sidebar key-validation removed

Test plan

  • npm test passes (55/55)
  • npm run lint passes (0 errors, 0 warnings)
  • npx tsc --noEmit clean
  • npm run build:functions emits build/functions/openai-proxy.js
  • Manual — Task 6: Update AppDefinition (3iszK8Gl7aaxLvxGyCOhgA prod, 7sjBFUzBxj1fRrI0eUBwLc test) to declare key as "type": "Secret" via CMA or Contentful UI
  • Manual: After AppDefinition update, verify sdk.parameters.installation.key returns *** in browser console
  • Manual: Deploy function bundle (npm run deploy:test) and confirm App Action invocation succeeds end-to-end in a test space

Notes

  • This PR is the code half of the fix. The AppDefinition Secret type declaration (Task 6 in the plan) must be applied separately before deploy — without it, the key is still plaintext in the browser even after this code lands.
  • UX change: generation is no longer streamed token-by-token; the full response is revealed at once. Streaming was architecturally incompatible with Contentful Functions' single-response model. During the wait, the generating state now shows a Forma 36 skeleton loader (grey pulsing text lines) in place of the old empty textarea.
  • The Stop Generating button now cancels an in-flight generation client-side: the App Action is raced against an AbortController signal, any late-arriving response is discarded, and the UI returns to the input view (a user stop is treated as a cancellation, not an error). The proxy call itself can't be terminated server-side — App Actions can't signal a running invocation and OpenAI's chat-completions API has no cancel endpoint — so the OpenAI request still completes/bills in the background.
  • Cleanup: removed the unreachable 429 error branch in GeneratedTextPanel (the proxy collapses all OpenAI failures into a generic Error with no status code, so the branch could never match — 429s now show the default generate error) and its orphaned deps, plus the unused ParametersMissingWarning component.

Generated with Claude Code

Tyler Pina (tylerpina) and others added 8 commits July 30, 2026 13:31
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…IS-295]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…IS-295]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… list [AIS-295]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace AI class usage in useAI.tsx with sdk.cma.appActionCall.createWithResponse
- Delete src/utils/aiApi/ directory (index.ts, aiHelpers.ts, handleAiApiErrors.ts) — no longer needed
- Remove key/validateApiKey/apiError from useSidebarParameters — key is now a Secret and unreadable in browser
- Remove apiError prop from DisplaySidebarWarning and Sidebar — key validation happens at generation time via App Action
- Update related specs for new interfaces
- Fix GeneratedTextPanel.tsx error type narrowing for unknown error from App Action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove `key` from parameter mocks (initParameters, happyPathParameter, generateRandomParameters), delete orphaned AiMock and baseUrl files, fix stale vi.mock calls in panel specs, and cast sdk.cma as any in useAI.spec.ts to allow appActionCall assignment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The randomApiKey variable was only used to populate the key field in the return object, which was already removed. It is now dead code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace unsafe (sdk.cma as any) casts with explicit type annotations to satisfy
@typescript-eslint/no-explicit-any lint rule. Change messages parameter type from
Symbol (256-char limit) to Text to accommodate JSON-stringified system prompts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tylerpina
Tyler Pina (tylerpina) requested review from a team as code owners July 30, 2026 20:26
@wiz-inc-38d59fb8d7

wiz-inc-38d59fb8d7 Bot commented Jul 30, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings -
Software Management Finding Software Management Findings -
Total -

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio.

Tyler Pina (tylerpina) and others added 9 commits July 31, 2026 11:33
Replace hardcoded definition ids in the new upsert-actions and
upsert-actions:test scripts with DEFINITIONS_APP_DEF_ID and
DEV_TESTING_APP_DEF_ID env vars, matching the org-id/token env
var pattern the deploy scripts already use. Also picks up the
build/functions copy and openai proxy secret-param fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use DEFINITIONS_APP_DEF_ID env var in the deploy script and remove
the deploy:test / upsert-actions:test variants; the env-var-driven
scripts cover the test org by swapping in the appropriate values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lockfile pinned @contentful/app-scripts and node-apps-toolkit to
npm.pkg.github.com download URLs (baked in from a local install with a
GitHub Packages token). CI has no such token, so npm ci failed with
E401 Unauthorized. Both packages are published to the public registry
with identical integrity hashes, so repoint the resolved URLs there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The newly added @contentful/app-scripts dependency pulls in esbuild,
whose install script must be explicitly allowlisted. allow-scripts
failed CI ("dependencies without configuration"). Add
@contentful/app-scripts>esbuild to lavamoat.allowScripts, matching
the entry other apps in the monorepo already use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI checks changed files against the master merge-base with
prettier@2.8.8. Format the six flagged files (line-wrapping only,
no logic changes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Since the OpenAI proxy fix removed token-by-token streaming, the
generating state left an empty textarea. Replace it with Forma 36's
SkeletonContainer/SkeletonBodyText — the grey pulsing lines used
elsewhere in Contentful — so the wait for the full response reads as
in-progress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sendStopSignal was a no-op. The proxy App Action is a single blocking
call with no server-side cancel, so wire the button to a client-side
abort: race the call against an AbortController signal, discard any
late-arriving response, and drop back to the input view. A user stop is
treated as a cancellation (GenerationAbortedError), not an error, so no
error UI is shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…AIS-295]

The proxy App Action collapses every OpenAI failure (429s included) into
a generic Error with no status code, so the `error.status === 429`
rate-limit branch could never match. Drop the dead branch along with its
now-orphaned deps: the Hyperlink/ExternalLinkIcon imports, the `error`
destructure, the `errorLink` style, and the three unused rateLimit*
strings in errorMessages. 429s now show the default generate error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ParametersMissingWarning had no production import — only its own spec
referenced it. Delete the component and its test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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