Skip to content

feat(launchdarkly): add admin-only flags summary endpoint - #3236

Open
sandsinh wants to merge 12 commits into
mainfrom
feat/admin-launchdarkly-flags-endpoint
Open

feat(launchdarkly): add admin-only flags summary endpoint#3236
sandsinh wants to merge 12 commits into
mainfrom
feat/admin-launchdarkly-flags-endpoint

Conversation

@sandsinh

@sandsinh sandsinh commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Please ensure your pull request adheres to the following guidelines:

  • make sure to link the related issues in this description. Or if there's no issue created, make sure you
    describe here the problem you're solving.
  • when merging / squashing, make sure the fixed issue references are visible in the commits, for easy compilation of release notes

If the PR is changing the API specification:

  • make sure you add a "Not implemented yet" note the endpoint description, if the implementation is not ready
    yet. Ideally, return a 501 status code with a message explaining the feature is not implemented yet.
  • make sure you add at least one example of the request and response.

If the PR is changing the API implementation or an entity exposed through the API:

  • make sure you update the API specification and the examples to reflect the changes.

If the PR is introducing a new audit type:

  • make sure you update the API specification with the type, schema of the audit result and an example

Related Issues

No tracked issue — internal/admin tooling endpoint requested to inspect real LaunchDarkly flag data for the experience-success-studio project without vendoring a static export snapshot (ff-export.json) into experience-success-studio-backoffice.

Summary

  • New GET /tools/launchdarkly/flags endpoint, admin-only (AccessControlUtil.hasAdminAccess()).
  • Fetches every flag for the experience-success-studio LD project, paginating through LD's REST API (GET /api/v2/flags/:project, 20/page default) by following _links.next.href until exhausted, capped at MAX_PAGES (50) as a safety bound.
  • Reduces each flag to { key, value }value is variations[0].value, the same field plg-onboarding/launchdarkly.js already reads, and exactly what experience-success-studio-backoffice's featureFlagParser.js expects. This is not a raw passthrough and does not forward caller query params — an earlier revision did both, and this description has been corrected to match the current implementation.
  • Defense-in-depth: the pagination next link is validated to stay within the requested flags path before being followed, so the LD API token can't be sent to an unexpected host/path if a response were ever tampered with.
  • Classified as INTERNAL_ROUTES in both required-capabilities.js and facs-capabilities.js — not exposed to S2S JWT consumers or FACS ReBAC, same bucket as GET /tools/proxy and GET /monitoring/drs-bp-pg-audit.
  • No OpenAPI doc entry added — matching the existing precedent that GET /tools/proxy (same internal-tooling class) is also undocumented there.
  • Investigated whether @adobe/spacecat-shared-launchdarkly-client (already used for this project/token in plg-onboarding/launchdarkly.js) could serve this: it only exposes single-flag reads/writes (getFeatureFlag, updateFallthroughVariation, updateVariationValue), no "list all flags" method, so this endpoint calls LD's REST API directly instead.

Test plan

  • npm run lint — clean
  • npm test — full suite green, including test/controllers/launchdarkly.test.js (admin gate, missing token, single-page and multi-page pagination, pagination-path validation, page-cap warning, no-variations flag, LD error status, fetch throwing) and updated test/routes/index.test.js wiring
  • Manual smoke test against dev with an admin session token (not yet run in this session)

Thanks for contributing!

GET /tools/launchdarkly/flags proxies LaunchDarkly's REST API for the
experience-success-studio project, returning the raw response as-is so
consumers (e.g. backoffice) can inspect real flag data without vendoring
a static export snapshot into a repo.

Co-Authored-By: Claude <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

Sandesh Sinha and others added 3 commits September 10, 2026 17:44
…eeds

Raw LD flag objects carry ~17 fields per flag and the "list flags" API
paginates at 20/page by default; the endpoint now follows `_links.next`
until exhausted and reduces each flag to { key, value } — the variation-0
value is the org/site targeting map that experience-success-studio-backoffice's
featureFlagParser.js (and the existing plg-onboarding LD code) actually reads.
Everything else (targeting rules, metadata, tags, etc.) was unused.

Co-Authored-By: Claude <noreply@anthropic.com>
…dpoint' into feat/admin-launchdarkly-flags-endpoint

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @sandsinh,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Request changes - two blocking issues to address before merge.
Complexity: HIGH - medium diff with API surface and FACS classification changes.
Changes: Adds an admin-only GET /tools/launchdarkly/flags endpoint that fetches and summarizes feature flags from the LaunchDarkly REST API (7 files).

Must fix before merge

  1. [Important] PR description contradicts the implementation - claims raw passthrough and query-string forwarding but code reshapes through toFlagSummary() and hardcodes params - src/controllers/launchdarkly.js:112 (details inline)
  2. [Important] Pagination follows _links.next.href without path-prefix validation - defense-in-depth gap - src/controllers/launchdarkly.js:57 (details inline)
Non-blocking (4): minor issues and suggestions
  • nit: response.json() is called before the response.ok check - if LD returns a non-JSON error page (e.g. an HTML 502), the SyntaxError loses the HTTP status context in the logged error - src/controllers/launchdarkly.js:45
  • nit: pagination loop exits silently at MAX_PAGES (2500 flags) without logging when more pages remain - src/controllers/launchdarkly.js:38
  • suggestion: the value field in toFlagSummary returns variations[0].value (a variation definition, not the evaluated value for any environment) - the JSDoc documents this choice clearly, but the bare field name may mislead future consumers who expect live flag state - src/controllers/launchdarkly.js:76
  • suggestion: the codebase already uses @adobe/spacecat-shared-launchdarkly-client in src/controllers/plg/plg-onboarding/launchdarkly.js for the same project and token - consider whether the shared client supports flag listing to centralize LD API interaction

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 8m 4s | Cost: $6.47 | Commit: 978a18133b0197d40a72f0a8ad2905ac6bf74b01
If this code review was useful, please react with 👍. Otherwise, react with 👎.

Comment thread src/controllers/launchdarkly.js
Comment thread src/controllers/launchdarkly.js Outdated
@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH labels Sep 10, 2026
Sandesh Sinha and others added 3 commits September 11, 2026 16:03
…r status

Addresses ai-pr-review blocking findings on PR #3236:
- Validate LD's `_links.next.href` starts with the requested flags path
  before following it, so the LD API token is never sent to an unexpected
  host/path if a response were ever tampered with.
- Check response.ok before calling response.json() on the error branch --
  an upstream non-JSON error body (e.g. an HTML 502) no longer masks the
  real HTTP status behind a JSON.parse failure.
- Log a warning if pagination hits the MAX_PAGES safety cap with more
  pages remaining, instead of silently truncating.

Co-Authored-By: Claude <noreply@anthropic.com>
…dpoint' into feat/admin-launchdarkly-flags-endpoint
@sandsinh sandsinh changed the title feat(launchdarkly): add admin-only raw flags passthrough endpoint feat(launchdarkly): add admin-only flags summary endpoint Sep 11, 2026
@sandsinh
sandsinh deployed to dev-branches September 11, 2026 10:54 — with GitHub Actions Active

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @sandsinh,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Request changes - one blocking issue with the response shape contract.
Complexity: HIGH - medium diff with API surface and FACS classification changes.
Changes: Adds an admin-only GET /tools/launchdarkly/flags endpoint that fetches and summarizes feature flags from the LaunchDarkly REST API (7 files).
Note: CI checks are still pending (build, it-postgres) - resolve before merge.

Must fix before merge

  1. [Important] toFlagSummary returns undefined for empty/missing variations, silently dropping the value key from the serialized response - src/controllers/launchdarkly.js:98 (details inline)
Non-blocking (2): minor issues and suggestions
  • nit: comments in src/routes/facs-capabilities.js:206 and src/routes/required-capabilities.js:183 say "raw flag passthrough" but the implementation reshapes flags through toFlagSummary() - the PR description was already corrected, but these inline comments were not updated to match
  • suggestion: pagination path check (startsWith(LD_FLAGS_PATH)) does not block same-host path traversal via ../ segments in the next link - consider comparing against the resolved pathname after URL construction - src/controllers/launchdarkly.js:72

Previously flagged, now resolved

  • PR description mismatch with implementation (corrected)
  • Pagination next-link validation added (prefix check on LD_FLAGS_PATH)
  • response.ok now checked before response.json()
  • MAX_PAGES cap now logs a warning when more pages remain

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 14m 14s | Cost: $8.06 | Commit: 20bf3723343ffa1d4b1028a620602f0bd65e69e8
If this code review was useful, please react with 👍. Otherwise, react with 👎.


/**
* Admin-only LaunchDarkly flags endpoint for the `experience-success-studio` project.
* Fetches every flag (paginating through LD's REST API) and returns only the fields

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (blocking): toFlagSummary returns { key, value: undefined } when variations is empty or absent. JSON.stringify silently drops undefined values, so the serialized response becomes { "key": "..." } with no value field, while flags with variations produce { "key": "...", "value": ... }. This inconsistent shape forces every consumer to handle the field being present or absent.

The fix:

value: flag.variations?.[0]?.value ?? null,

This keeps the shape uniform and makes "no variations" explicit. The test at line 159 documents the current behavior and would need updating to expect { key: 'FF_no-variations', value: null }.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 62fd065 — value now falls back to null instead of undefined, so the response shape is uniform (value key always present) regardless of whether a flag has variations. Also addressed both non-blocking nits from the same review: pagination-link validation now resolves the href against our base URL and checks the resolved origin+pathname (closes the ../ traversal / different-origin gap), and the stale 'raw flag passthrough' comments in facs-capabilities.js and required-capabilities.js were updated to describe the actual reshaped response.

…heck

Addresses ai-pr-review round 2 on PR #3236:
- toFlagSummary now returns value: null (not undefined) for a flag with
  no variations, so the response shape is uniform instead of the `value`
  key silently disappearing under JSON serialization for some flags.
- Pagination link validation now resolves the next href against our own
  base URL and checks the resolved origin + pathname, instead of a raw
  string prefix check -- closes a `../` path-traversal gap and an
  absolute different-origin URL, either of which could have sent the LD
  API token to an unexpected destination.
- Updated stale "raw flag passthrough" comments in facs-capabilities.js
  and required-capabilities.js to match the actual reshaped response.

Co-Authored-By: Claude <noreply@anthropic.com>

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @sandsinh,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Approve - all prior blocking findings addressed; no new blocking issues.
Complexity: HIGH - medium diff with API surface and FACS classification changes.
Changes: Adds an admin-only GET /tools/launchdarkly/flags endpoint that fetches and summarizes feature flags from the LaunchDarkly REST API (7 files).
Note: CI checks are still pending (build, it-postgres) - resolve before merge.

Non-blocking (4): minor issues and suggestions
  • nit: JSDoc @param for launchDarklyController in src/routes/index.js:205 still says "passthrough controller" - the same terminology was corrected in facs-capabilities.js and required-capabilities.js but missed here
  • suggestion: the Authorization header receives the raw LD API token without a Bearer prefix (correct for LD's API), but a future maintainer may "fix" this by adding one - a one-line comment on the header noting LD expects the raw token would prevent that - src/controllers/launchdarkly.js:50
  • suggestion: the catch block logs e.message but not e.status (which the code sets explicitly for LD API errors on line 57) - including e.status in the log line would tell on-call whether to rotate the token (401) or back off (429) without reproducing the call - src/controllers/launchdarkly.js:146
  • suggestion: no test exercises a response body where body.items is undefined (vs empty array) - the ?? [] fallback on line 63 is doing real work and deserves a test that proves it - test/controllers/launchdarkly.test.js

Previously flagged, now resolved

  • toFlagSummary returns value: null instead of undefined for empty variations (uniform response shape)
  • Pagination link validation upgraded from naive startsWith on raw string to resolved-URL origin+pathname check (closes path-traversal and different-origin vectors)
  • Stale "raw flag passthrough" comments updated to "flags summary" in facs-capabilities.js and required-capabilities.js

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 3s | Cost: $6.66 | Commit: 62fd065f225c55c793e8d930b082192d6a3e4dc9
If this code review was useful, please react with 👍. Otherwise, react with 👎.

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

Labels

ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants