An audit loop walking config fields with `Object.keys`. Each iteration tries to read `config[k]` but `k` is `string`, not `keyof Config`. Index access errors out under strict mode; under loose mode, downstream type inference degrades.
The vulnerable diff
// lib/audit-config.ts
interface Config { host: string; port: number; useTLS: boolean }
function audit(config: Config) {
// BUG: keys typed as string, index access via string returns any
Object.keys(config).forEach(k => {
check(config[k]); // error: Element implicitly has an 'any' type
});
}What is wrong
`Object.keys()` has return type `string[]` by design — at runtime an object can have additional keys beyond what the type system knows about. This makes type-safe iteration awkward. The common patterns are: cast the result to `(keyof T)[]` (asserting no extra keys), use a `for...of` over `Object.entries`, or use a typed helper. Each has trade-offs.
The attack
Compile error under strict mode:
error TS7053: Element implicitly has an 'any' type because
expression of type 'string' can't be used to index type 'Config'.Common workarounds spread `any` downstream, weakening type safety further.
Mesrai's review comment
mesraipilot · Bot · reviewed 20 sec ago
[mesrai] [code-review] [Language] [Type-Widening] [low]
`Object.keys(config)` returns string[] not (keyof Config)[].
Three options:
// Option 1: cast (assert no extra keys)
(Object.keys(config) as (keyof typeof config)[]).forEach(k => {
check(config[k]);
});
// Option 2: Object.entries — types both key and value better
Object.entries(config).forEach(([k, v]) => {
// k: string, v: Config[keyof Config] = string | number | boolean
check(v);
});
// Option 3: typed helper
function typedKeys<T extends object>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
typedKeys(config).forEach(k => check(config[k]));
Prefer Object.entries when you need both key and value — it types
the value via T[keyof T] which is usually what you want.The fix
// lib/audit-config.ts — fixed
function audit(config: Config) {
Object.entries(config).forEach(([k, v]) => {
check(v);
});
}`Object.entries` types the value as a union of all the field types. No cast needed in many cases.
Why human review missed it
Object.keys widening is a TS-vs-JS design tension. Many devs solve it with broad casts that spread `any`. Mesrai flags every `Object.keys(typed).forEach(k => obj[k])` shape and recommends `Object.entries` or a typed helper.
Related rules + further reading
Mesrai rule pack: language/object-keys-widening — flags Object.keys + index access without typed-keys cast.
TypeScript GitHub: long-standing discussion on Object.keys typing.
Common in config / state-walking code.
Takeaway
Object.entries types both sides. Or typedKeys helper. Avoid raw Object.keys + index access on typed objects.