Skip to content

feat(tracer): resolve identifiers assigned an object literal - #689

Open
Edneam wants to merge 2 commits into
NodeSecure:masterfrom
Edneam:feat/tracer-resolve-object-identifiers
Open

feat(tracer): resolve identifiers assigned an object literal#689
Edneam wants to merge 2 commits into
NodeSecure:masterfrom
Edneam:feat/tracer-resolve-object-identifiers

Conversation

@Edneam

@Edneam Edneam commented Aug 2, 2026

Copy link
Copy Markdown

Summary

Closes #639.

VariableTracer can resolve an identifier back to its value for Literal and TemplateLiteral initializers (literalIdentifiers), but not for an identifier assigned an object literal:

const x = "hello";      // resolvable via literalIdentifiers
const y = { a: 1 };     // NOT resolvable back to its ObjectExpression

This has a concrete effect on the log-usage probe: pino() and winston.createLogger() config detection (customLevels, useOnlyCustomLevels, levels) only works when the config object is passed inline:

pino({ customLevels: { foo: 35 }, useOnlyCustomLevels: true }); // detected correctly

but silently falls through to the default method list (or, for winston.createLogger, produces no warning at all) when the same object is passed via a variable, which is an extremely common pattern:

const opts = { customLevels: { foo: 35 }, useOnlyCustomLevels: true };
pino(opts); // before this PR: treated as if it had no config at all

Changes

  • VariableTracer.ts: add a new public objectIdentifiers: Map<string, ESTree.ObjectExpression>, populated in the existing ObjectExpression branch of #walkVariableDeclaratorInitialization for top-level assignments only (const x = {...}, not nested object values), mirroring how literalIdentifiers is populated for Literal/TemplateLiteral.
  • log-usage.ts: in both createPinoTracerListener and createWinstonCreateLoggerTracerListener, resolve the config argument via tracer.objectIdentifiers when it's an Identifier instead of bailing out when it isn't an inline ObjectExpression.

I deliberately did not touch literalIdentifiers itself (adding an object type there) since its .value: string field is consumed as a plain string by several other probes (sql-injection, isRequire, isWeakBcrypt, isMonkeyPatch, etc.) — a separate map keeps this change isolated to the object-resolution use case with no risk to existing consumers.

Tests

  • test/VariableTracer/assignments.spec.ts: two new tests confirming objectIdentifiers is populated for a top-level object literal assignment and correctly not populated for a nested object value.
  • test/probes/log-usage.spec.ts: two new tests (one for pino(), one for winston.createLogger()) confirming the config-via-variable case now resolves the custom levels correctly. Verified against a version of the fix reverted locally that without it, the pino case falls back to default methods and the winston case produces zero warnings — confirming this is a real, previously-silent gap.

Verification

node --test-reporter=spec --test "./test/VariableTracer/**/*.spec.ts" "./test/probes/log-usage.spec.ts" "./test/probes/sql-injection.spec.ts" "./test/probes/crypto/**/*.spec.ts" "./test/probes/isRequire/**/*.spec.ts" "./test/probes/isMonkeyPatch.spec.ts"

All pass (179/179) when run with a RegExp.escape polyfill preloaded to work around this sandbox only having Node 22 available (the repo requires Node >=24; I don't have Node 24 in this environment, and confirmed the RegExp.escape is not a function failures reproduce identically on a clean master checkout — they're unrelated to this change, from an unrelated obfuscator check that fires on every .analyse() call).

Full suite on Node 22, no polyfill: 739 tests, 701 pass, 38 fail — same 38 pre-existing RegExp.escape-related failures as on a clean master checkout (0 new failures from this change). tsc/eslint also couldn't run locally (both reference private @openally/config.* packages not resolvable outside NodeSecure's own setup), so I can't run those two locally either, but neither touches runtime logic.

Added a changeset (minor, matching the existing convention for new detection capability).

Edneam added 2 commits August 2, 2026 18:15
VariableTracer could only resolve identifiers back to their literal
value for Literal and TemplateLiteral initializers. An identifier
assigned an object literal (const opts = {...}) had no way to be
resolved back to its ObjectExpression node, so the log-usage probe's
pino()/winston.createLogger() config detection only worked when the
config object was passed inline, e.g. pino({ customLevels: {...} }),
not when passed as a separately-declared variable, e.g.:

  const opts = { customLevels: {...} };
  pino(opts);

Add a VariableTracer.objectIdentifiers map, populated for top-level
object-literal assignments alongside the existing literalIdentifiers
map, and use it in log-usage.ts to resolve the pino()/
winston.createLogger() config argument when it's an Identifier
instead of an inline ObjectExpression.

Fixes NodeSecure#639
@Edneam
Edneam requested a review from a team as a code owner August 2, 2026 14:16
@changeset-bot

changeset-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 214eb2c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@nodesecure/js-x-ray Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(Tracer) add support to resolve identifier for object

2 participants