mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 11:17:07 +00:00
fix: stop Doctor exposing hash fragments as unset variables (#1558)
Classify Compose stderr unset-variable warnings into intentional references vs literal-dollar fragments from secret values. Adds env-literal-dollar preflight rule and safe remediation text. Fixes #1550.
This commit is contained in:
@@ -82,11 +82,37 @@ const envUnset: PreflightRule = {
|
||||
title: `Unset variable ${name}`,
|
||||
message: `"${name}" is referenced by the Compose model but is not set in the environment or any consulted env file. Compose substitutes an empty string, which often breaks the container silently.`,
|
||||
sourcePath: name,
|
||||
remediation: `Define ${name} in a .env or env_file, or give it a default with \${${name}:-value}.`,
|
||||
remediation: `Define ${name} in a .env or env_file, or give it a default with \${${name}:-value}. If the value is a literal secret or hash containing \`$\`, escape \`$\` as \`$$\` in Compose YAML or single-quote the value in an env file.`,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
const LITERAL_DOLLAR_REMEDIATION =
|
||||
'If this was intended as a Compose variable, define it in the project environment or give it a default. '
|
||||
+ 'If it is part of a literal secret or hash, escape literal dollar signs as `$$` in Compose YAML or single-quote the value in an env file.';
|
||||
|
||||
const envLiteralDollar: PreflightRule = {
|
||||
id: 'env-literal-dollar',
|
||||
run(ctx) {
|
||||
return ctx.literalDollarWarnings.map(w => {
|
||||
const likelySecret = w.likelySecret;
|
||||
const title = likelySecret
|
||||
? 'Literal dollar sign in likely secret value may be interpolated'
|
||||
: 'Literal dollar sign in environment value may be interpolated';
|
||||
const keyHint = w.envKey ? ` for "${w.envKey}"` : '';
|
||||
return {
|
||||
ruleId: 'env-literal-dollar',
|
||||
severity: 'high' as const,
|
||||
title,
|
||||
message: `Compose treated a literal $ sequence inside an environment value${keyHint} as variable interpolation and may substitute an empty string for part of the value.`,
|
||||
sourcePath: w.envKey,
|
||||
remediation: LITERAL_DOLLAR_REMEDIATION,
|
||||
service: w.service,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const envFileMissing: PreflightRule = {
|
||||
id: 'env-file-missing',
|
||||
run(ctx) {
|
||||
@@ -730,6 +756,7 @@ const sensitiveServiceBroadExposure: PreflightRule = {
|
||||
export const PREFLIGHT_RULES: PreflightRule[] = [
|
||||
renderFailed,
|
||||
envUnset,
|
||||
envLiteralDollar,
|
||||
envFileMissing,
|
||||
portConflictNode,
|
||||
portConflictInternal,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { EffectiveModel } from './effectiveModel';
|
||||
import type { ExposureIntent } from '../network/types';
|
||||
import type { LiteralDollarWarning } from '../../helpers/unsetEnvClassification';
|
||||
|
||||
/** Graded severity of a single preflight finding. */
|
||||
export type PreflightSeverity = 'blocker' | 'high' | 'warning' | 'info';
|
||||
@@ -87,8 +88,10 @@ export interface PreflightContext {
|
||||
renderable: boolean;
|
||||
/** Redacted + truncated render error, or null. */
|
||||
renderError: string | null;
|
||||
/** Variable names Compose reported as unset (defaulted to empty string). */
|
||||
/** Variable names Compose reported as unset (intentional references only). */
|
||||
unsetEnvVars: string[];
|
||||
/** Literal `$` sequences misread as variables; never includes fragment names. */
|
||||
literalDollarWarnings: LiteralDollarWarning[];
|
||||
/** Declared `env_file:` paths that are required but absent on disk (names only). */
|
||||
missingEnvFiles: MissingEnvFile[];
|
||||
/** Service names parsed from the literal source file (pre-render). */
|
||||
|
||||
Reference in New Issue
Block a user