Skip to content

fix: keep empty and valueless SAML attributes in profile.attributes#413

Open
arpitjain099 wants to merge 1 commit into
node-saml:masterfrom
arpitjain099:fix/empty-saml-attributes-227
Open

fix: keep empty and valueless SAML attributes in profile.attributes#413
arpitjain099 wants to merge 1 commit into
node-saml:masterfrom
arpitjain099:fix/empty-saml-attributes-227

Conversation

@arpitjain099

@arpitjain099 arpitjain099 commented Jun 5, 2026

Copy link
Copy Markdown

Fixes #227

Problem

When a SAML assertion carries an attribute that exists but has no real value, node-saml currently loses that information in profile.attributes:

  • An attribute with no AttributeValue child at all, for example <saml2:Attribute Name="employee_id"/>, is dropped from profile.attributes entirely. Google Workspace emits these in the wild.
  • An attribute whose AttributeValue is present but empty, for example <AttributeValue/> with xsi:type="xs:string", comes back as undefined.

In both cases a consumer cannot tell a valueless attribute apart from one the IdP never sent: {} and { employee_id: null } should not collapse to the same thing.

The SAML 2.0 Core spec is explicit about both shapes:

  • Section 1219: within an <AttributeStatement>, if the SAML attribute exists but has no values, the <AttributeValue> element MUST be omitted. The attribute itself still exists.
  • Section 1246: if a SAML attribute includes an empty value such as the empty string, the corresponding <AttributeValue> element MUST be empty (serialized as <AttributeValue/>).

Fix

src/saml.ts attribute extraction now keeps these attributes instead of discarding them:

  • No AttributeValue child is represented as null (the attribute exists, no value).
  • An empty AttributeValue is represented as the empty string "" rather than undefined, matching the xs:string empty-value semantics from section 1246.

Populated single-value, object-valued (nested NameID), and multi-valued attributes are parsed exactly as before. The single-value-versus-array branch is untouched.

Before / after

For <saml2:Attribute Name="employee_id"/>:

  • before: profile.attributes does not contain employee_id
  • after: profile.attributes.employee_id === null

For <saml2:Attribute Name="x"><AttributeValue/></saml2:Attribute>:

  • before: profile.attributes.x === undefined
  • after: profile.attributes.x === ""

Tests

Added two tests under the validatePostResponse xml signature checks suite, including the exact null-attribute case from the issue:

  • "An attribute with no AttributeValue should be null"
  • "An empty AttributeValue should be an empty string"

Both fail on master (the first throws expected {} to have property 'attributeName', the second expected undefined to equal '') and pass with this change. I also updated the two existing tests that asserted the old drop/undefined behavior, since the issue calls that behavior out as incorrect:

  • accept response with an attributeStatement element without attributeValue now expects evilcorp.roles to be present as null.
  • the former An undefined value given with an object should still be undefined is renamed to An empty AttributeValue should be an empty string and asserts "".

Full suite is green (273 passing), and lint and prettier pass on the changed files.

Compatibility note

This is a behavior change for consumers that relied on valueless attributes being absent or undefined, so it fits a semver-major release. That lines up with the breaking-change release discussed in the issue thread.

Summary by CodeRabbit

  • Bug Fixes

    • Improved SAML attribute handling to properly preserve missing attributes with null values instead of omitting them.
    • Fixed attribute value mapping to correctly return empty strings for empty AttributeValue nodes.
  • Tests

    • Updated test cases to validate new SAML attribute processing behavior.

An attribute with no AttributeValue child (e.g. <Attribute Name="x"/>)
was dropped from profile.attributes entirely, and an attribute with an
empty AttributeValue (e.g. <AttributeValue/> for an xs:string) was
returned as undefined. Both lose information: a consumer cannot tell a
valueless attribute apart from one the IdP never sent.

Per SAML Core section 1219 an attribute that exists with no values keeps
the attribute and is now represented as null. Per section 1246 an empty
AttributeValue is the empty string and is now represented as "" instead
of undefined. Populated and multi-valued attributes are unchanged.

Fixes node-saml#227

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d741ae9c-d1ba-4bca-8b30-203964ab71b4

📥 Commits

Reviewing files that changed from the base of the PR and between 25c434a and 85a1b13.

📒 Files selected for processing (2)
  • src/saml.ts
  • test/tests.spec.ts

📝 Walkthrough

Walkthrough

The PR refines SAML attribute parsing to distinguish between missing attributes and attributes that exist but lack values. Empty AttributeValue nodes now map to empty strings, and Attribute elements without AttributeValue children now record null values instead of being skipped entirely.

Changes

SAML Attribute Value Representation

Layer / File(s) Summary
Attribute value mapping and null handling
src/saml.ts
The attrValueMapper returns value._ ?? "" for AttributeValue nodes with no child elements to produce empty strings. Attributes without an AttributeValue child are now recorded in profileAttributes as null and initialized in profile[name] to explicitly represent present-but-empty attributes.
Attribute parsing test cases
test/tests.spec.ts
Test expectations updated to assert empty AttributeValue elements produce "" and Attribute elements without AttributeValue children produce null in profile attributes, replacing prior expectations that omitted or undefined these values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through SAML trees,
Finding empty values with ease—
Null marks what exists but stays bare,
Empty strings fill the parsing air,
Now profiles show what IPs declare! 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: keep empty and valueless SAML attributes in profile.attributes' clearly and concisely summarizes the main change: preserving empty and valueless attributes in the profile attributes object.
Description check ✅ Passed The description provides a comprehensive summary of the change, addresses issue #227, includes SAML spec references, explains the use-case (Google Workspace), documents the before/after behavior, and confirms tests are included and passing.
Linked Issues check ✅ Passed The code changes fully address the objectives from issue #227: attributes without AttributeValue are now represented as null, empty AttributeValue is represented as empty string, tests are added/updated, and the behavior aligns with SAML Core spec sections 1219 and 1246.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the handling of empty and valueless SAML attributes as specified in issue #227; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

[BUG] Empty and Null Attributes are not returned in profile.attributes

1 participant