Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 5s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 2m41s
Build App (Preview) / build-windows (pull_request) Successful in 4m53s
Build App (Preview) / build-linux (pull_request) Successful in 7m5s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
Round 4 review findings: - Disclose and warn on a custom Docker image the import would set (HIGH): it's the image every project container is created from, so an undisclosed change here was a sharper version of the redirected-base-URL problem round 3 already flagged for the model backends. - Recreate a running gateway container when an import restores a new secret with the shape unchanged (MEDIUM): reconcile_gateway's shape comparison can't see a secret-only change, so the container would otherwise keep serving old key material indefinitely. - Report keychain write failures back to the caller instead of only logging them (MEDIUM): apply_settings_import now returns SettingsImportOutcome with secret_restore_warnings so a partial restore can't read as unqualified success. - Pin a hash of the previewed file's ciphertext and refuse to apply if it changed on disk (MEDIUM): closes a TOCTOU between preview and apply. - Sanitize and cap every free-form string a preview surfaces, and move the warning boxes above the replace list in the UI (MEDIUM): an unbounded base URL or image name could otherwise push the security warnings below the scroll fold. - Validate the Docker socket path on import the same as the SSH key and CA cert paths (LOW): it was the one mounted host path validate_settings_update didn't cover. - Fix ExportedSecrets::is_empty() to treat whitespace-only as blank, like every other secret-presence check in this feature (LOW). - Authenticate the file header as AEAD associated data (LOW, defense in depth) and correct two doc comments that overstated the password not being cached.
68 lines
3.4 KiB
TypeScript
68 lines
3.4 KiB
TypeScript
import type { SettingsImportPreview } from "./types";
|
|
|
|
/** Named things a `SettingsImportPreview` says an import will change, for
|
|
* `ImportSettingsModal`'s confirmation list. Does not include anything
|
|
* `describeImportWarnings` covers — those get their own, more visible
|
|
* treatment rather than blending into this list. */
|
|
export function describeImport(preview: SettingsImportPreview): string[] {
|
|
const items: string[] = ["Your global settings (all of them — this replaces what's here now)"];
|
|
if (preview.custom_env_var_count > 0) {
|
|
items.push(
|
|
`${preview.custom_env_var_count} global custom env var${preview.custom_env_var_count === 1 ? "" : "s"}`,
|
|
);
|
|
}
|
|
if (preview.has_claude_code_settings) items.push("Global Claude Code settings");
|
|
if (preview.gateway_model_count > 0) {
|
|
items.push(`${preview.gateway_model_count} gateway model${preview.gateway_model_count === 1 ? "" : "s"}`);
|
|
}
|
|
if (preview.has_claude_oauth_token) items.push("Your shared Claude login");
|
|
if (preview.has_gateway_api_key) items.push("The gateway provider API key");
|
|
if (preview.has_gateway_master_key) items.push("The gateway master key");
|
|
if (preview.has_web_terminal_access_token) items.push("The web terminal access token");
|
|
if (preview.ollama_base_url) items.push(`Ollama server: ${preview.ollama_base_url}`);
|
|
if (preview.llamacpp_base_url) items.push(`llama.cpp server: ${preview.llamacpp_base_url}`);
|
|
if (preview.openai_compatible_base_url) {
|
|
items.push(`OpenAI-compatible server: ${preview.openai_compatible_base_url}`);
|
|
}
|
|
if (preview.gateway_api_base) items.push(`Gateway upstream: ${preview.gateway_api_base}`);
|
|
if (preview.image_source === "custom") {
|
|
items.push(`Docker image: ${preview.custom_image_name ?? "(no image name set)"}`);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
/**
|
|
* Things about an import that deserve more attention than a bullet in a
|
|
* long list — deliberately its own function rather than a flag inside
|
|
* `describeImport`: a setting that turns on a network-listening service is
|
|
* exactly the kind of change a "your settings were replaced" summary is bad
|
|
* at surfacing, on purpose or (if the file came from someone else) not.
|
|
*
|
|
* A token that arrives with the terminal left *off* gets its own warning
|
|
* too, distinct from the "enables it now" one: `start_web_terminal` only
|
|
* mints a fresh token when none is already set, so a planted token here
|
|
* would silently become live the next time someone flips the terminal on
|
|
* through the UI, with no import-time signal that it wasn't freshly
|
|
* generated.
|
|
*
|
|
* A custom Docker image gets a warning every time, not just on change: it's
|
|
* the image every project container is created from, so it's worth calling
|
|
* out regardless of what was configured before the import.
|
|
*/
|
|
export function describeImportWarnings(preview: SettingsImportPreview): string[] {
|
|
const warnings: string[] = [];
|
|
if (preview.enables_web_terminal) {
|
|
warnings.push("Enables the remote web terminal, which listens on your network.");
|
|
} else if (preview.has_web_terminal_access_token) {
|
|
warnings.push(
|
|
"Includes a web terminal access token that will activate the next time the web terminal is turned on.",
|
|
);
|
|
}
|
|
if (preview.image_source === "custom") {
|
|
warnings.push(
|
|
`Runs every project container from a custom Docker image: ${preview.custom_image_name ?? "(no image name set)"}.`,
|
|
);
|
|
}
|
|
return warnings;
|
|
}
|