The Hacker now blocks {{ and also bans the word secret in your input. But the whole config object is in the template context, and the secret value is part of it.
The same greeting portal, but the filter blocks both the double braces and the word secret. The secret still lives inside the config object in the context, so you can dump the whole object without naming the banned word.
You bypassed the word ban by rendering the parent object and read the secret.
Why the solution worked
Banning a word in the input does not protect the value: reaching it through the parent object or another syntax bypasses the ban, and in real engines SSTI is escalated to reach global objects and full code execution. The fix: do not build the template from user input, do not put secrets in the template context, and render in an isolated, least-privilege environment.
The root cause
Blocking a word and a delimiter does not stop access to sensitive data while it sits in the context; dumping the parent object reveals it. The root is pasting input into the template and keeping the secret in the render context.
How the code is fixed
Do not put secrets in the template context at all; banning words does not protect the object that holds them.
Vulnerable code
const name = req.body.name;
if (name.includes('{{') || name.includes('secret')) return bad(); // word blacklist
engine.render('Hello ' + name, { config }); // secret sits in the context
Safe code
// Input as data; no secret in the template context; sandboxed render.
engine.render('Hello {{ name }}', { name: req.body.name });
Impact in the real world
Dumping context objects is a known technique to bypass word filters and leak secrets in template injection.
How defenders detect it
Defenders keep secrets out of the render context, never render templates from input, and watch for context-object dumping attempts.