Skip to content

fix(ui): render Run in Postman locally and stop double-escaping its URL - #57

Merged
Tushar-TG-14 merged 3 commits into
tigergraph:mainfrom
ngarakapati:fix/postman-url-escaping
Sep 25, 2026
Merged

Tushar-TG-14 merged 3 commits into
tigergraph:mainfrom
ngarakapati:fix/postman-url-escaping

Conversation

@ngarakapati

@ngarakapati ngarakapati commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes two bugs in the Run in Postman control. The first is what readers see on production today.

1. The button is blocked by the site CSP

On the live REST API page the button renders as a broken-image icon plus its alt text.

The docs site sends:

img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com
        https://www.tigergraph.com https://cdnjs.cloudflare.com https://i.ibb.co
        https://lh3-lh6.googleusercontent.com https://gblobscdn.gitbook.com
        https://firebasestorage.googleapis.com

https://run.pstmn.io is not on that list, so the browser refuses to load Postman's hosted button.svg. The asset itself is healthy: it returns 200 image/svg+xml with or without a tigergraph.com referer, so this is not hotlink protection.

This escaped review because Netlify deploy previews send no CSP header at all, so the button renders correctly in preview and only breaks behind www.tigergraph.com.

Fix

Vendor Postman's own button.svg into the theme at src/img/run-in-postman.svg and serve it from the bundle. The control keeps its official appearance, and img-src 'self' covers it, so no CSP change and no infrastructure ticket are needed.

The src uses @root.uiRootPath rather than a bare uiRootPath. This partial renders inside a {{#with @root.page.url}} block, where the bare form resolves to empty and emits an absolute /img/... path that 404s. A first pass had exactly that bug and the local build caught it.

2. The href was double-escaped

The served URL contained &, which browsers decode to &, so Postman parsed the query as action, amp;source, and amp;collection-url.

Asciidoctor applies its specialcharacters substitution to header attribute values, so page-postman-url already arrives with & in place of &. The template escaped it a second time with {{this}}, now {{{this}}}.

This one was not user-visible: action=collection/fork survived, and Postman takes the collection ID from the URL path, so forking always worked. What was lost is the source=rip_markdown attribution and the workspaceId.

Note for whoever reviews: with the escaping fixed, workspaceId now reaches Postman, and its page then reports data-postman-visibility="private" instead of public. I could not confirm whether that blocks an anonymous user, because that logic runs client-side. Worth one incognito click after merge. If the fork dialog fails, drop workspaceId from :page-postman-url: in cloud-docs.

Verification

Built cloud-docs locally with Antora against both bundles:

  • Old bundle reproduces the live href byte for byte, including &.
  • New bundle emits single ampersands, run.pstmn.io appears nowhere in the generated site, and the vendored src resolves to a real file (../../../_/img/run-in-postman.svg), matching how site.css resolves on the same page.
  • The SVG survives gulp-imagemin intact: same 128x32 dimensions and the same #FF6C37 fill.

Test plan

  • npx gulp bundle passes lint and build; bundle rebuilt and committed.
  • Generated site contains no reference to run.pstmn.io.
  • Vendored button resolves on every page that renders the control.
  • page-postman-url href renders with single &.
  • Preview: button renders as Postman's orange pill, aligned with Copy page.
  • Click through in incognito and confirm the fork dialog opens.

Asciidoctor applies specialcharacters substitution to header attribute
values, so page-postman-url already arrives with & in place of &.
Escaping it again in the template emitted &, which browsers
decoded to &, so Postman received amp;collection-url instead of
collection-url and the fork button had no collection to open.
The docs site CSP allows img-src 'self' and a short allowlist that does
not include run.pstmn.io, so Postman's hosted button.svg was blocked in
production and the control rendered as broken-image plus alt text. The
Netlify previews send no CSP, so this only showed up once the pages were
behind www.tigergraph.com.

Inline the Postman mark from simple-icons (CC0), the same way the
ChatGPT, Claude, and Cursor marks are already handled, and give the
anchor the copy control's shell. No remote asset, so no CSP change is
needed. The page-postman-url href is unchanged.
@ngarakapati ngarakapati changed the title fix(ui): stop double-escaping the Run in Postman URL fix(ui): render Run in Postman locally and stop double-escaping its URL Sep 25, 2026
Serve Postman's own button from the UI bundle instead of drawing a
local substitute, so the control keeps its official appearance while
still satisfying the site CSP, which allows img-src 'self' but does not
list run.pstmn.io.

The src is referenced via @root.uiRootPath: the partial renders inside a
{{#with page.url}} block, where a bare uiRootPath resolves to empty and
would emit an absolute /img/... path that 404s.
@ngarakapati

Copy link
Copy Markdown
Contributor Author

Alternative: keep Postman's hosted button and allow it in the CSP

Recording this for future reference, since this PR takes the other route.

This PR vendors button.svg into the theme so img-src 'self' covers it. The other valid fix is to keep loading Postman's hosted asset and add their CDN to the site's Content Security Policy. Both work. The difference is only who has to make the change and what we have to maintain afterwards.

Why the hosted asset fails today

www.tigergraph.com sends this header:

img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com
        https://www.tigergraph.com https://cdnjs.cloudflare.com https://i.ibb.co
        https://lh3.googleusercontent.com https://lh4.googleusercontent.com
        https://lh5.googleusercontent.com https://lh6.googleusercontent.com
        https://gblobscdn.gitbook.com https://firebasestorage.googleapis.com

https://run.pstmn.io is absent, so the browser blocks the image and the control renders as a broken-image icon plus its alt text. The asset itself is fine: it returns 200 image/svg+xml with or without a tigergraph.com referer, so this is not hotlink protection on Postman's side.

Steps to take the CSP route instead

  1. Find the right place to edit. The header is not in cloud-docs, not in antora-ui, and not in doc-site/netlify.toml. That last one is the easy mistake: it already sends a permissive img-src * data: blob:, which would allow Postman, but production overrides it. Responses from www.tigergraph.com/docs show server: nginx/1.22.1 behind CloudFront, so the effective policy is set on that proxy. Ask whoever owns the public website edge.

  2. Append one origin to img-src, leaving every other directive untouched:

    img-src 'self' data: ... https://firebasestorage.googleapis.com https://run.pstmn.io;
    

    Scheme and host only. No path. No script-src change is needed, because we embed the button as an image rather than using Postman's JavaScript embed. (Their HTML-friendly snippet loads button.js from the same host and would also need script-src.)

  3. Point the template back at the hosted asset in src/partials/page-actions.hbs:

    <img src="https://run.pstmn.io/button.svg" alt="Run in Postman" width="128" height="32">
  4. Delete the vendored copy at src/img/run-in-postman.svg, then rebuild with npx gulp bundle and commit the bundle.

  5. Verify on production, not on a preview. Netlify deploy previews send no CSP header at all, which is exactly why this bug reached production unnoticed. Load the real page and confirm DevTools reports no run.pstmn.io violation on img-src.

Trade-offs

Vendored (this PR) CSP allowlist
Infrastructure change None One directive on the nginx/CloudFront edge
Tracks Postman's restyles No, re-download manually Yes, automatically
Extra external request No Yes
Breaks if Postman's CDN has an outage No Yes

Worth noting for anything we add later: because previews carry no CSP, any new external asset will look fine in preview and can still be blocked in production. Production is the only place this class of bug shows up.

@Tushar-TG-14
Tushar-TG-14 merged commit ff75ae8 into tigergraph:main Sep 25, 2026
1 check passed
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.

2 participants