mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-09 21:00:56 -07:00
feat(security): Introduce Conseca framework (#13193)
This commit is contained in:
committed by
GitHub
parent
05bc0399f3
commit
dde844dbe1
@@ -5,7 +5,11 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { safeLiteralReplace, truncateString } from './textUtils.js';
|
||||
import {
|
||||
safeLiteralReplace,
|
||||
truncateString,
|
||||
safeTemplateReplace,
|
||||
} from './textUtils.js';
|
||||
|
||||
describe('safeLiteralReplace', () => {
|
||||
it('returns original string when oldString empty or not found', () => {
|
||||
@@ -99,3 +103,60 @@ describe('truncateString', () => {
|
||||
expect(truncateString('', 5)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeTemplateReplace', () => {
|
||||
it('replaces all occurrences of known keys', () => {
|
||||
const tmpl = 'Hello {{name}}, welcome to {{place}}. {{name}} is happy.';
|
||||
const replacements = { name: 'Alice', place: 'Wonderland' };
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe(
|
||||
'Hello Alice, welcome to Wonderland. Alice is happy.',
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores keys not present in replacements', () => {
|
||||
const tmpl = 'Hello {{name}}, welcome to {{unknown}}.';
|
||||
const replacements = { name: 'Bob' };
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe(
|
||||
'Hello Bob, welcome to {{unknown}}.',
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores extra keys in replacements', () => {
|
||||
const tmpl = 'Hello {{name}}';
|
||||
const replacements = { name: 'Charlie', age: '30' };
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe('Hello Charlie');
|
||||
});
|
||||
|
||||
it('handles empty template', () => {
|
||||
expect(safeTemplateReplace('', { key: 'val' })).toBe('');
|
||||
});
|
||||
|
||||
it('handles template with no placeholders', () => {
|
||||
expect(safeTemplateReplace('No keys here', { key: 'val' })).toBe(
|
||||
'No keys here',
|
||||
);
|
||||
});
|
||||
|
||||
it('prevents double interpolation (security check)', () => {
|
||||
const tmpl = 'User said: {{userInput}}';
|
||||
const replacements = {
|
||||
userInput: '{{secret}}',
|
||||
secret: 'super_secret_value',
|
||||
};
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe(
|
||||
'User said: {{secret}}',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles values with $ signs correctly (no regex group substitution)', () => {
|
||||
const tmpl = 'Price: {{price}}';
|
||||
const replacements = { price: '$100' };
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe('Price: $100');
|
||||
});
|
||||
|
||||
it('treats special replacement patterns (e.g. "$&") as literal strings', () => {
|
||||
const tmpl = 'Value: {{val}}';
|
||||
const replacements = { val: '$&' };
|
||||
expect(safeTemplateReplace(tmpl, replacements)).toBe('Value: $&');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,3 +82,24 @@ export function truncateString(
|
||||
}
|
||||
return str.slice(0, maxLength) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely replaces placeholders in a template string with values from a replacements object.
|
||||
* This performs a single-pass replacement to prevent double-interpolation attacks.
|
||||
*
|
||||
* @param template The template string containing {{key}} placeholders.
|
||||
* @param replacements A record of keys to their replacement values.
|
||||
* @returns The resulting string with placeholders replaced.
|
||||
*/
|
||||
export function safeTemplateReplace(
|
||||
template: string,
|
||||
replacements: Record<string, string>,
|
||||
): string {
|
||||
// Regex to match {{key}} in the template string. The regex enforces string naming rules.
|
||||
const placeHolderRegex = /\{\{(\w+)\}\}/g;
|
||||
return template.replace(placeHolderRegex, (match, key) =>
|
||||
Object.prototype.hasOwnProperty.call(replacements, key)
|
||||
? replacements[key]
|
||||
: match,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user