Claude Cursor GitHub Copilot Skill

pci-payment-ui-security-review

Statically review payment-page frontend code for PCI-DSS-relevant defects in the browser/DOM slice only — raw PAN collection in self-controlled inputs instead of Stripe hosted fields, card data persisted client-side or to analytics, raw card data POSTed to a first-party endpoint,

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_pci-payment-ui-security-review-febe32a.zip · 14 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/pci-payment-ui-security-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

PCI Payment UI Security Review

Purpose

Review payment-collection frontend code — checkout pages, card-entry forms, and any script loaded on a page that collects cardholder data — for the frontend-specific defect classes that keep raw cardholder data out of the DOM and out of merchant-controlled JavaScript: raw PAN/CVV collection in a self-controlled <input> instead of Stripe's iframe-isolated hosted fields, card data persistence to localStorage/sessionStorage/analytics/logs, raw card data POSTed to a first-party endpoint instead of tokenized first, and third-party scripts loaded on the payment page without Subresource Integrity (SRI). This skill exists so the review stays anchored to the frontend tokenization boundary and script-integrity surface instead of drifting into a general "payments code review."

When to use

Use this skill when the user asks to:

  • review a checkout page or card-entry form for PCI-relevant frontend risk,
  • assess whether card-number, expiry, or CVV fields are collected safely (hosted fields vs. raw <input>),
  • audit which scripts load on a payment-collection page and whether they carry Subresource Integrity,
  • assess SAQ-A scope reduction claims for a page that outsources card collection to Stripe Elements/Payment Element,
  • perform a pre-launch frontend security review of a Stripe-based payment integration.

Do not use this skill for:

  • a full PCI-DSS compliance audit — this skill covers only the frontend slice (client-side script inventory/integrity, hosted-field isolation, tokenization boundary). Server-side cardholder-data-environment (CDE) segmentation, network firewalling, key management, encryption-at-rest, access control, and the remaining PCI-DSS requirement families are out of scope; route those to a dedicated infrastructure/compliance review,
  • a non-payment page with no card-data collection or third-party script surface — general frontend security review is a different scope,
  • a bug that requires live traffic capture (a proxy/network trace confirming what actually left the browser) to confirm exploitation — static analysis proves the structural risk (the code path that would expose or persist raw PAN/CVV), not that raw card data has already left the browser in production.

Context7 Documentation Protocol

  • Resolve the Stripe library ID with resolve-library-id before citing any Stripe Elements/Payment Element or tokenization-mechanism claim; use query-docs to corroborate the specific API behavior (e.g., stripe.createToken(cardElement), stripe.confirmPayment(), stripe.confirmCardPayment()) rather than relying on memory.
  • Label every Stripe-API behavioral claim documentation-based (or repo evidence if confirmed directly against fetched Context7 doc content) — do not assert current Stripe API shape from training memory alone.
  • Label every PCI-DSS requirement claim (6.4.3, 11.6.1, or the general SAQ-A scope-reduction rationale) standard-based — this skill does not verify a merchant's actual PCI compliance status or attestation; it evaluates whether the frontend code matches the documented pattern the standard and Stripe's architecture rely on.
  • If Context7 is unavailable, fall back to the official_docs URLs in this skill's metadata.json and label the claim documentation-based, unverified against current release.
  • Read package.json/script tags first to confirm which Stripe integration is in use (@stripe/stripe-js + Elements, Payment Element, or a legacy/custom integration) — the safe idiom and API names differ; do not assume Elements is in use without confirming.

Lean operating rules

  • Raw PAN/CVV collection outside an iframe-isolated hosted field, and any client-side persistence of raw cardholder data, default to HIGH severity. This is a security-scoped skill: do not downgrade a raw-PAN-in-DOM finding to MEDIUM because "it's only in a dev/staging build" — the structural pattern is the risk, not its current deployment target.
  • Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this form might expose card data" without showing the specific <input> element, the specific .value read, or the specific POST body construction is not a valid finding — it is a guess.
  • Do not flag <CardNumberElement />, <CardExpiryElement />, <CardCVCElement />, or <PaymentElement /> usage as a defect — these are Stripe's iframe-rendered hosted fields; the PAN/CVV they collect stays inside iframes controlled by Stripe and is never accessible to merchant JavaScript. Only a self-controlled <input> (e.g., <input type="text" id="cardnumber" />) that application JS reads via .value is the raw-pan-input defect.
  • Treat any localStorage, sessionStorage, in-memory app store, or analytics/logging call that could carry a PAN, CVV, or full cardholder data payload as a HIGH finding regardless of whether the value is currently populated in the traced code path — check the shape of the object being persisted, not just its current runtime value.
  • Treat a first-party POST (fetch/XMLHttpRequest/form submit to a first-party endpoint) carrying raw card fields (PAN, CVV, expiry) as a HIGH finding. The safe pattern tokenizes first via the Stripe API (stripe.createToken(cardElement) or the Payment Element confirmation flow) and POSTs only the resulting token or PaymentIntent/PaymentMethod ID to the first-party endpoint.
  • Treat any third-party <script src="..."> loaded on a payment-collection page without an integrity attribute (Subresource Integrity, e.g. a sha256- hash) as a finding — cite PCI-DSS v4 requirement 6.4.3 (script inventory, script authorization, and integrity control) as the standard-based grounding. Do not accept "we trust this vendor" as a substitute for a visible integrity attribute or equivalent cryptographic verification.
  • Stripe's hosted fields (CardNumberElement, CardExpiryElement, CardCVCElement, PaymentElement) render inside sandboxed iframes controlled by Stripe, not merchant code — the browser's same-origin policy and the iframe sandbox boundary are what keep merchant JavaScript from ever reading raw PAN/CVV values. A missing-iframe-isolation finding applies when card fields are collected via manual <input> elements with no sandboxed hosted-field equivalent in use.
  • When reviewing broader payment-page change-detection posture, note PCI-DSS v4 requirement 11.6.1 (automated change-detection/alerting for unauthorized script modification on payment pages) as a standard-based expectation — but do not fabricate a finding about a change-detection mechanism you cannot observe in the frontend code under review; note it as an open question instead.
  • Never execute, build, or run application code, and never send live requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
  • Load only the reference needed for the concern in scope.

References

Load these only when needed:

Response minimum

Return, at minimum:

  • the payment-collection page(s), card-entry form markup, script tags, and/or client-side persistence/network calls in scope,
  • ranked findings with file:line evidence, defect category (raw-pan-input, card-data-persistence, self-posted-card-data, unsigned-third-party-script, or missing-iframe-isolation), the concrete data-flow trace (the raw <input>/.value read, the localStorage/analytics call, the POST body construction, or the missing integrity attribute), and a fix sketch matching Stripe's documented idiom (CardNumberElement, PaymentElement, stripe.createToken, or an integrity/SRI attribute),
  • for every finding involving PAN/CVV/cardholder data exposure, an explicit statement of whether the value stays inside a Stripe-controlled iframe (safe) or is reachable by merchant JavaScript (unsafe) — never approve on the assumption isolation exists without tracing it,
  • evidence level per finding (repo evidence, documentation-based, standard-based, or inference), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited or confirmed non-compliant,
  • verdict (approve / approve-with-notes / block),
  • open questions or scope the review could not cover — explicitly restate that this is not a full PCI-DSS audit: server-side cardholder-data-environment segmentation, key management, network controls, and non-script requirement families are out of scope and require a dedicated compliance/infrastructure review.
Files (vanguard-frontier-agentic)
  • references
    • hosted-fields-and-tokenization.md 7.7 KB
      # Hosted Fields and the Tokenization Boundary
      
      Use this reference when reviewing card-entry form markup, or investigating the `raw-pan-input`, `card-data-persistence`, or `self-posted-card-data` defect classes.
      
      ## What people get wrong
      
      The naive assumption is:
      
      > "As long as I send the card data over HTTPS to my own server, it's protected in transit, so a plain `<input>` for the card number is fine."
      
      Wrong for PCI-DSS purposes. Transport encryption protects data in transit between the browser and the first-party server, but it does nothing to prevent the merchant's own JavaScript — or a compromised/malicious third-party script sharing the page — from reading the raw PAN/CVV the instant it sits in a DOM `<input>` or in memory as a plain JS value. The entire architectural point of Stripe's hosted fields is to remove that reachability, not merely to encrypt what is already reachable.
      
      ## Officially grounded tokenization flow (Context7 Stripe documentation)
      
      - `stripe.createToken(cardElement)` converts data collected by Stripe Elements into a **single-use token**, passed securely to the server — the merchant's own code never handles the raw values that produced the token.
      - Raw card data (PAN, CVV) collected by `CardNumberElement`, `CardExpiryElement`, and `CardCVCElement` stays **inside iframes controlled by Stripe**, never accessible to merchant JavaScript.
      - `stripe.confirmPayment()` and `stripe.confirmCardPayment()` tokenize directly as part of confirming the PaymentIntent, without ever exposing raw values to the page.
      - The merchant receives only the token, or the PaymentIntent/PaymentMethod confirmation result — never the raw card data itself.
      - **Key architectural guarantee:** Elements render inside sandboxed iframes; merchant code never sees or touches the PAN or CVV. The browser's same-origin policy prevents scripts on the merchant page — including the merchant's own code and any co-located third-party script — from reading iframe content.
      
      ## Defect class 1: `raw-pan-input`
      
      Raw PAN collection in a self-controlled `<input>` element.
      
      - **Dangerous:** `<input type="text" id="cardnumber" />` with application JS reading `.value` to obtain the card number. The moment card-number digits exist as a plain string in application-reachable memory or the DOM, they are reachable by merchant JS, any injected script, and any browser extension with page access.
      - **Safe:** `<CardNumberElement />` (iframe-rendered by Stripe; no app JS access to the value inside).
      - **Verification targets:** Grep for `<input` elements with `type="text"`/`type="number"`/`name`/`id` attributes suggestive of card fields (`card`, `pan`, `cardnumber`, `ccnum`), and confirm whether the surrounding component imports and uses `CardNumberElement`/`PaymentElement` instead, or reads `.value` directly from a raw input.
      
      ## Defect class 2: `missing-iframe-isolation`
      
      No iframe/hosted-field isolation for card-data collection generally (PAN, CVV, *or* expiry).
      
      - **Dangerous:** Manual `<input>` fields for PAN, CVV, and/or expiry with no iframe sandboxing — a homegrown card form.
      - **Safe:** `<CardNumberElement />`, `<CardExpiryElement />`, `<CardCVCElement />`, or the unified `<PaymentElement />`, all of which render inside a sandboxed iframe boundary that the merchant page cannot reach into.
      - This is the broader structural finding that `raw-pan-input` is one specific instance of — flag it whenever *any* of the three card fields (not just the PAN) is collected outside a hosted field.
      
      ## Defect class 3: `card-data-persistence`
      
      Card data persisted to a client-side store, `localStorage`/`sessionStorage`, analytics, or logs.
      
      - **Dangerous:** `localStorage.setItem('card', {pan, cvv, exp})`, a Vuex/Redux/Pinia `store` action that commits raw card fields to persisted state, or an analytics event (`analytics.track('checkout_attempt', {cardNumber, cvv})`) that includes cardholder data fields.
      - **Safe:** Persist only the tokenized `paymentMethod` ID (e.g., `store.commit('setPaymentMethod', paymentMethod.id)`) — never raw PAN, CVV, or full cardholder data.
      - **Verification targets:** Grep for `localStorage.setItem`/`sessionStorage.setItem` calls, and for `store`/state-management writes and analytics/logging calls, in files reachable from the payment form. For each match, inspect the literal object shape being persisted for PAN- or CVV-shaped fields, even under generic key names — a field literally named `PAN` or `CVV`, or one holding a 13–19 digit numeric string or 3–4 digit CVV pattern, is the signal to trace, regardless of the variable name chosen.
      
      ## Defect class 4: `self-posted-card-data`
      
      Raw card fields POSTed to a first-party endpoint.
      
      - **Dangerous:** `fetch('/api/pay', {body: {pan, cvv, exp}})` — raw card fields constructed directly into a POST body and sent to the merchant's own `/api/pay` first-party endpoint, with no tokenization step beforehand.
      - **Safe:** `stripe.createToken(cardElement).then(token => fetch('/api/pay', {body: {token: token.id}}))` — the Stripe API tokenizes first; only the resulting token is POSTed to the first-party endpoint.
      - **Verification targets:** Grep for `fetch(`/`XMLHttpRequest`/form `action=` submissions targeting a first-party API path from payment-form components. For each, trace the request body's construction backward: does it reference raw card fields (`pan`, `cardNumber`, `cvv`, `exp`), or does it reference a token/PaymentIntent/PaymentMethod object produced by a preceding Stripe API call?
      
      ## Minimal safe implementation pattern
      
      ```jsx
      // Safe: hosted fields + tokenize-before-POST
      import { CardNumberElement, CardExpiryElement, CardCVCElement, useStripe, useElements } from '@stripe/react-stripe-js'
      
      function CheckoutForm() {
        const stripe = useStripe()
        const elements = useElements()
      
        async function handleSubmit(e) {
          e.preventDefault()
          const cardElement = elements.getElement(CardNumberElement)
          // Raw PAN/CVV never leave the Stripe iframe; only a token is produced.
          const { token } = await stripe.createToken(cardElement)
          // Only the token is persisted or sent to the first-party endpoint.
          await fetch('/api/pay', { method: 'POST', body: JSON.stringify({ token: token.id }) })
        }
      
        return (
          <form onSubmit={handleSubmit}>
            <CardNumberElement />
            <CardExpiryElement />
            <CardCVCElement />
          </form>
        )
      }
      ```
      
      Anti-pattern (do not approve):
      
      ```html
      <!-- WRONG: raw PAN/CVV in a self-controlled input, read directly by app JS -->
      <input type="text" id="cardnumber" />
      <input type="text" id="cvv" />
      <script>
        function submitPayment() {
          const pan = document.getElementById('cardnumber').value
          const cvv = document.getElementById('cvv').value
          localStorage.setItem('lastCard', JSON.stringify({ pan, cvv })) // card-data-persistence
          fetch('/api/pay', { method: 'POST', body: JSON.stringify({ pan, cvv }) }) // self-posted-card-data
        }
      </script>
      ```
      
      ## Adversarial checklist
      
      Before clearing a payment form as safe on the tokenization boundary, answer these:
      
      - Is card number, expiry, and CVV collection fully delegated to `CardNumberElement`/`CardExpiryElement`/`CardCVCElement`/`PaymentElement`, or does any field use a manual `<input>`?
      - Does any code path read `.value` from a manual card-shaped input?
      - Does any `localStorage`/`sessionStorage`/store/analytics/log call persist an object containing PAN-, CVV-, or cardholder-data-shaped fields?
      - Does the first-party POST body reference raw card fields, or does it reference only a Stripe token/PaymentIntent/PaymentMethod ID produced by a preceding `stripe.createToken`/`stripe.confirmPayment`/`stripe.confirmCardPayment` call?
      
      If any answer reveals raw PAN/CVV reachable by merchant JS at rest, in a POST body, or in persisted state, the finding is HIGH and structural — report it even without a reproduced data-exfiltration incident.
      
    • script-integrity-and-scope.md 6.2 KB
      # Script Integrity and SAQ-A Scope Reduction
      
      Use this reference when reviewing third-party script tags loaded on a payment-collection page for Subresource Integrity, or when assessing whether a page's architecture plausibly supports an SAQ-A scope-reduction claim.
      
      ## What people get wrong
      
      The naive assumption is:
      
      > "This third-party script is from a well-known vendor's CDN over HTTPS, so it's safe to load on the checkout page."
      
      Wrong for PCI-DSS purposes. HTTPS protects the script in transit from the CDN to the browser; it does not protect against the CDN itself being compromised, the vendor's build pipeline being compromised, or a supply-chain attack that swaps the script's content while its URL stays identical (the class of attack behind real-world Magecart-style card-skimming incidents). Subresource Integrity closes exactly this gap: it lets the browser refuse to execute a script whose fetched content does not match a cryptographic hash pinned at authorization time.
      
      ## PCI-DSS v4 grounding (standard-based)
      
      **6.4.3 — Script Inventory, Authorization & Integrity Control:**
      
      - All scripts loaded on payment collection pages must be inventoried and approved.
      - Scripts must be authorized before loading (script authorization).
      - Integrity verification is required via Subresource Integrity (SRI) or an equivalent cryptographic verification mechanism (e.g., a SHA-256 hash).
      
      **11.6.1 — Change Detection on Payment Pages:**
      
      - Automated change-detection/alerting mechanisms are required for unauthorized code additions or modifications to payment pages.
      - This is intended to detect malicious script injections, DOM tampering, and compromise attempts in near-real time, independent of the SRI control above.
      
      Both of these are standard requirement citations (`standard-based`) — this skill can confirm from frontend code whether SRI is present on a given script tag, but it cannot confirm from static frontend review alone whether a merchant has an operating script-inventory/authorization *process* or a functioning 11.6.1 change-detection deployment. Note any such gap as an open question rather than asserting non-compliance.
      
      ## Defect class: `unsigned-third-party-script`
      
      Third-party script loaded without Subresource Integrity.
      
      - **Dangerous:**
        ```html
        <script src="https://cdn.example.com/analytics.js"></script>
        ```
        No `integrity` attribute means the browser will execute whatever content the CDN serves at request time, with no cryptographic check against what was reviewed/authorized.
      - **Safe:**
        ```html
        <script src="https://cdn.example.com/analytics.js"
                integrity="sha256-C6CB9UYIS9UJeqinPHWTHVqh/E1uhG5Twh76tviuROE="
                crossorigin="anonymous"></script>
        ```
        The `integrity` attribute pins a SHA-256 (or SHA-384/SHA-512) hash; the browser refuses to execute the script if the fetched bytes don't match. `crossorigin="anonymous"` is required alongside `integrity` for cross-origin script resources so the browser can perform the integrity check.
      - **Verification targets:** Grep every `<script src="...">` tag rendered on a payment-collection page (including scripts injected via a tag-manager snippet, if visible in source) for the presence of an `integrity` attribute. A same-origin/first-party script (served from the merchant's own domain) is lower risk but still benefits from SRI if served via a CDN in front of first-party assets; the primary finding target is third-party origins.
      - A missing `integrity` attribute on a third-party script on a payment page is a HIGH finding regardless of the vendor's reputation — reputation is not a substitute for script authorization and integrity verification under 6.4.3.
      
      ## SAQ-A scope reduction (standard-based)
      
      SAQ-A is the PCI-DSS self-assessment questionnaire for merchants that have **fully outsourced** all cardholder data collection and processing to a PCI-validated third party (e.g., Stripe), such that the merchant's own systems never store, process, or transmit cardholder data and never touch it even transiently in the browser.
      
      For a payment page's frontend architecture to plausibly support an SAQ-A claim:
      
      - Every card field (PAN, expiry, CVV) must be collected exclusively via Stripe hosted fields (`CardNumberElement`/`CardExpiryElement`/`CardCVCElement`) or the unified `PaymentElement` — no manual `<input>` anywhere in the flow (would otherwise be a `raw-pan-input`/`missing-iframe-isolation` finding).
      - No client-side persistence of raw cardholder data anywhere in the flow (would otherwise be a `card-data-persistence` finding).
      - No first-party POST of raw card fields anywhere in the flow — only tokens/PaymentIntent/PaymentMethod IDs cross to the first-party server (would otherwise be a `self-posted-card-data` finding).
      
      This skill can confirm the frontend evidence for or against these three conditions. It **cannot** confirm the merchant's actual SAQ-A eligibility determination or attestation — that is a compliance/assessor decision that also depends on the merchant's payment page hosting model (e.g., whether the payment page itself is served from PCI-relevant infrastructure) and other SAQ-A eligibility criteria outside this skill's frontend-only scope. Label any SAQ-A-adjacent conclusion `standard-based` and explicitly note the full-audit exclusion.
      
      ## Adversarial checklist
      
      Before clearing a payment page's script surface:
      
      - Does every third-party `<script src="...">` on the page carry an `integrity` attribute with a `sha256-` (or stronger) hash and a `crossorigin` attribute?
      - Is there any script loaded dynamically (via `document.createElement('script')` or a tag-manager container) that bypasses static `integrity` attribution entirely? Flag this as a distinct, harder-to-verify risk — dynamically injected scripts cannot carry a static `integrity` attribute in the same way, and the review should note this as an open question requiring the tag-manager's own vendor controls to be checked separately.
      - If asked about SAQ-A: do all three frontend conditions above hold, with no raw-PAN, persistence, or self-posted-data findings anywhere in the reviewed flow?
      - Is there any observable client-side change-detection or integrity-monitoring code on the page (relevant to 11.6.1), or is this an open question requiring server/ops-side confirmation?
      
    • workflow-and-output.md 8.1 KB
      # Review Workflow and Findings Contract
      
      Use this reference for the step-by-step review procedure and the required output shape. Load the other two references only for the specific defect class the payment page under review actually raises.
      
      ## Prerequisites
      
      - Confirm the page under review actually collects or handles cardholder data: a checkout form, a saved-card management page, or any page that loads Stripe.js or renders card-entry fields. If no such page exists in scope, this skill does not apply.
      - Identify the Stripe integration shape in use: `@stripe/stripe-js` + `Elements` (`CardNumberElement`/`CardExpiryElement`/`CardCVCElement`), the unified `Payment Element`, or a legacy/custom card-collection integration. The safe idiom and the specific API calls to verify differ by shape.
      - Remember the scope boundary throughout: this is a **frontend-only** PCI-DSS review. Do not extend findings into server-side cardholder-data-environment segmentation, network firewalling, key management, or encryption-at-rest — those require a dedicated infrastructure/compliance review and are out of scope here.
      
      ## Workflow
      
      1. **Locate every card-entry surface.** For each checkout/payment form, identify whether card number, expiry, and CVV are collected via Stripe hosted fields (`CardNumberElement`, `CardExpiryElement`, `CardCVCElement`, or `PaymentElement`) or via manual `<input>` elements. See `references/hosted-fields-and-tokenization.md` for the `raw-pan-input` and `missing-iframe-isolation` decision tree.
      2. **Trace client-side persistence and analytics calls.** Grep for `localStorage.setItem`, `sessionStorage.setItem`, in-memory app `store` writes, and analytics/logging calls (`analytics.track`, `console.log`, error-reporting SDK calls) reachable from the payment form's state. For each, inspect the shape of the persisted/logged object for PAN, CVV, or other cardholder data fields. See `references/hosted-fields-and-tokenization.md` for the `card-data-persistence` decision tree.
      3. **Trace network calls from the payment form.** For each `fetch`/`XMLHttpRequest`/form submission triggered by the payment form, determine whether raw card fields (PAN, CVV, expiry) are POSTed directly to a first-party endpoint, or whether a Stripe tokenization call (`stripe.createToken(cardElement)`, `stripe.confirmPayment()`, `stripe.confirmCardPayment()`) runs first and only the resulting token/PaymentIntent/PaymentMethod ID is POSTed. See `references/hosted-fields-and-tokenization.md` for the `self-posted-card-data` decision tree.
      4. **Enumerate every script tag loaded on the payment page.** For each third-party `<script src="...">`, check for an `integrity` attribute (Subresource Integrity) and a `crossorigin` attribute. See `references/script-integrity-and-scope.md` for the `unsigned-third-party-script` decision tree.
      5. **Assess SAQ-A scope-reduction plausibility (if asked).** If the user asks about SAQ-A eligibility, check whether card data collection is fully outsourced to Stripe hosted fields/Payment Element with no raw card data ever touching the merchant's own JavaScript or servers. See `references/script-integrity-and-scope.md`.
      6. **Produce ranked findings** using the output contract below.
      
      ## Decision tree
      
      - Card number/expiry/CVV collected via a manual `<input>` element (e.g., `<input type="text" id="cardnumber" />`) with application JS reading `.value` → **HIGH** finding, `raw-pan-input` and `missing-iframe-isolation`. The safe pattern is `<CardNumberElement />`/`<PaymentElement />`.
      - Card number/expiry/CVV collected via `<CardNumberElement />`, `<CardExpiryElement />`, `<CardCVCElement />`, or `<PaymentElement />` → not a finding; these are Stripe's iframe-rendered hosted fields, and the browser's same-origin policy prevents merchant JS from reading iframe content.
      - A `localStorage`/`sessionStorage`/store/analytics call persists an object containing PAN, CVV, or full card data (even if named generically, e.g. `paymentInfo`) → **HIGH** finding, `card-data-persistence`. The safe pattern persists only a tokenized `paymentMethod` ID.
      - A `localStorage`/`sessionStorage`/store/analytics call persists only a Stripe token, PaymentIntent ID, or PaymentMethod ID (no raw card fields) → not a finding.
      - A first-party POST body is constructed from raw card fields (`pan`, `cardNumber`, `cvv`, `exp`) without a preceding Stripe tokenization call → **HIGH** finding, `self-posted-card-data`.
      - A first-party POST body is constructed from a Stripe token (`token.id`) or PaymentIntent/PaymentMethod confirmation result, with the tokenization call (`stripe.createToken`, `stripe.confirmPayment`, `stripe.confirmCardPayment`) preceding it on the same path → not a finding.
      - A third-party `<script src="...">` on the payment page has no `integrity` attribute → **HIGH** finding, `unsigned-third-party-script` (PCI-DSS v4 6.4.3, standard-based).
      - A third-party `<script src="..." integrity="sha256-..." crossorigin="anonymous">` on the payment page → not a finding.
      - User asks about SAQ-A scope reduction and card collection is fully outsourced to Stripe hosted fields/Payment Element with no raw card data touching merchant JS or servers → plausible SAQ-A alignment, label `standard-based`, and note this skill does not verify the merchant's full SAQ-A questionnaire or attestation status.
      - User asks about SAQ-A scope reduction and any `raw-pan-input`, `card-data-persistence`, or `self-posted-card-data` finding is present → SAQ-A eligibility is not plausible on the frontend evidence reviewed; flag the specific defect blocking it.
      
      ## Output contract
      
      Every response from this skill must return:
      
      1. **Scope** — the payment-collection page(s), card-entry form markup, script tags, and/or client-side persistence/network calls reviewed.
      2. **Ranked findings** — each with file:line, defect category (`raw-pan-input` / `card-data-persistence` / `self-posted-card-data` / `unsigned-third-party-script` / `missing-iframe-isolation`), the concrete data-flow trace (the raw input read, the persistence/analytics call, the POST body construction, or the missing integrity attribute), and a fix sketch matching Stripe's documented pattern.
      3. **Iframe-isolation status per PAN/CVV-handling finding** — an explicit statement of whether the value stays inside a Stripe-controlled iframe (safe) or is reachable by merchant JavaScript (unsafe); never infer isolation exists without tracing it.
      4. **Evidence level per finding** — `repo evidence`, `documentation-based`, `standard-based`, or `inference`. Label structural risk findings as structural risk — do not imply confirmed exploitation or confirmed non-compliance without live evidence (e.g., a captured network trace, an actual PCI assessor finding).
      5. **Verdict** — approve / approve-with-notes / block.
      6. **Open questions or out-of-scope items** — always restate explicitly that this is not a full PCI-DSS audit: server-side CDE segmentation, network controls, key management, and non-script requirement families are out of scope.
      
      ## When to push back
      
      Push back if the user asks to:
      
      - approve a raw `<input>` card field because "we sanitize/mask it before sending" — masking display does not change that raw PAN/CVV briefly exists in merchant-readable DOM/JS state; only Stripe's iframe-isolated hosted fields keep it out of merchant JS entirely,
      - treat a third-party script as safe because "it's from a reputable vendor" without a visible `integrity` attribute — vendor reputation is not equivalent cryptographic verification under PCI-DSS v4 6.4.3,
      - skip the client-side persistence check because "we only store it in memory, not localStorage" — an in-memory app `store` holding raw PAN/CVV is still a `card-data-persistence` finding if it is reachable by other application code, logging, or a crash-reporting SDK,
      - call a page "SAQ-A eligible" solely because it uses Stripe.js somewhere on the page — SAQ-A eligibility requires that raw card data never reaches merchant-controlled JS or servers at all; a page that uses Stripe.js for one flow but also POSTs raw card fields elsewhere does not qualify,
      - treat this review as a full PCI-DSS compliance sign-off — this skill covers only the frontend script/tokenization slice; state the full-audit exclusion explicitly in every response.
      
  • metadata.json 2 KB
    {
      "id": "pci-payment-ui-security-review",
      "name": "PCI Payment UI Security Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Reviews payment-collection frontend code for the PCI-DSS-relevant frontend defect classes: raw PAN/CVV collection in self-controlled inputs instead of Stripe hosted fields, card data persisted client-side or to analytics, raw card data POSTed to a first-party endpoint before tokenization, and third-party scripts loaded without Subresource Integrity — grounded via Context7 Stripe documentation and PCI-DSS v4 script-security requirements (standard-based).",
      "source_type": "original",
      "official_docs": [
        "https://docs.stripe.com/js/elements_object/create",
        "https://docs.stripe.com/security/guide#validating-pci-compliance",
        "https://www.pcisecuritystandards.org/document_library/",
        "https://www.pcisecuritystandards.org/documents/SAQ-A-r2-v4_0.pdf"
      ],
      "security_notes": "This skill's entire scope is security-critical: raw PAN/CVV collection outside a Stripe-controlled iframe, client-side persistence of cardholder data, and unsigned third-party scripts on payment pages are all cardholder-data-exposure or code-tampering vectors. Every finding in this skill defaults to HIGH severity unless proven otherwise with concrete evidence of iframe isolation, tokenization-before-POST, or a visible integrity attribute. This is the FRONTEND slice of PCI-DSS only — it is NOT a full PCI-DSS audit; server-side cardholder-data-environment segmentation, network controls, and key management are explicitly out of scope. Static-review-only skill: it reads and greps payment-page source but never executes, builds, or runs application code, and never sends live requests.",
      "last_verified": "2026-07-03",
      "path": "skills/frontend/pci-payment-ui-security-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 9.6 KB
    ---
    name: pci-payment-ui-security-review
    description: Statically review payment-page frontend code for PCI-DSS-relevant defects in the browser/DOM slice only — raw PAN collection in self-controlled inputs instead of Stripe hosted fields, card data persisted client-side or to analytics, raw card data POSTed to a first-party endpoint, and third-party scripts loaded without Subresource Integrity — grounded in Stripe's own tokenization docs and PCI-DSS v4 script-security requirements.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-03"
      category: security
    ---
    
    # PCI Payment UI Security Review
    
    ## Purpose
    
    Review payment-collection frontend code — checkout pages, card-entry forms, and any script loaded on a page that collects cardholder data — for the frontend-specific defect classes that keep raw cardholder data out of the DOM and out of merchant-controlled JavaScript: raw PAN/CVV collection in a self-controlled `<input>` instead of Stripe's iframe-isolated hosted fields, card data persistence to `localStorage`/`sessionStorage`/analytics/logs, raw card data POSTed to a first-party endpoint instead of tokenized first, and third-party scripts loaded on the payment page without Subresource Integrity (SRI). This skill exists so the review stays anchored to the frontend tokenization boundary and script-integrity surface instead of drifting into a general "payments code review."
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - review a checkout page or card-entry form for PCI-relevant frontend risk,
    - assess whether card-number, expiry, or CVV fields are collected safely (hosted fields vs. raw `<input>`),
    - audit which scripts load on a payment-collection page and whether they carry Subresource Integrity,
    - assess SAQ-A scope reduction claims for a page that outsources card collection to Stripe Elements/Payment Element,
    - perform a pre-launch frontend security review of a Stripe-based payment integration.
    
    Do not use this skill for:
    
    - a full PCI-DSS compliance audit — this skill covers **only the frontend slice** (client-side script inventory/integrity, hosted-field isolation, tokenization boundary). Server-side cardholder-data-environment (CDE) segmentation, network firewalling, key management, encryption-at-rest, access control, and the remaining PCI-DSS requirement families are out of scope; route those to a dedicated infrastructure/compliance review,
    - a non-payment page with no card-data collection or third-party script surface — general frontend security review is a different scope,
    - a bug that requires live traffic capture (a proxy/network trace confirming what actually left the browser) to confirm exploitation — static analysis proves the structural risk (the code path that *would* expose or persist raw PAN/CVV), not that raw card data has already left the browser in production.
    
    ## Context7 Documentation Protocol
    
    - Resolve the Stripe library ID with `resolve-library-id` before citing any Stripe Elements/Payment Element or tokenization-mechanism claim; use `query-docs` to corroborate the specific API behavior (e.g., `stripe.createToken(cardElement)`, `stripe.confirmPayment()`, `stripe.confirmCardPayment()`) rather than relying on memory.
    - Label every Stripe-API behavioral claim `documentation-based` (or `repo evidence` if confirmed directly against fetched Context7 doc content) — do not assert current Stripe API shape from training memory alone.
    - Label every PCI-DSS requirement claim (6.4.3, 11.6.1, or the general SAQ-A scope-reduction rationale) `standard-based` — this skill does not verify a merchant's actual PCI compliance status or attestation; it evaluates whether the frontend code matches the documented pattern the standard and Stripe's architecture rely on.
    - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`.
    - Read `package.json`/script tags first to confirm which Stripe integration is in use (`@stripe/stripe-js` + Elements, `Payment Element`, or a legacy/custom integration) — the safe idiom and API names differ; do not assume Elements is in use without confirming.
    
    ## Lean operating rules
    
    - Raw PAN/CVV collection outside an iframe-isolated hosted field, and any client-side persistence of raw cardholder data, default to HIGH severity. This is a security-scoped skill: do not downgrade a raw-PAN-in-DOM finding to MEDIUM because "it's only in a dev/staging build" — the structural pattern is the risk, not its current deployment target.
    - Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this form might expose card data" without showing the specific `<input>` element, the specific `.value` read, or the specific POST body construction is not a valid finding — it is a guess.
    - Do not flag `<CardNumberElement />`, `<CardExpiryElement />`, `<CardCVCElement />`, or `<PaymentElement />` usage as a defect — these are Stripe's iframe-rendered hosted fields; the PAN/CVV they collect stays inside iframes controlled by Stripe and is never accessible to merchant JavaScript. Only a self-controlled `<input>` (e.g., `<input type="text" id="cardnumber" />`) that application JS reads via `.value` is the raw-pan-input defect.
    - Treat any `localStorage`, `sessionStorage`, in-memory app store, or analytics/logging call that could carry a PAN, CVV, or full cardholder data payload as a HIGH finding regardless of whether the value is currently populated in the traced code path — check the shape of the object being persisted, not just its current runtime value.
    - Treat a first-party POST (`fetch`/`XMLHttpRequest`/form submit to a first-party endpoint) carrying raw card fields (PAN, CVV, expiry) as a HIGH finding. The safe pattern tokenizes first via the Stripe API (`stripe.createToken(cardElement)` or the Payment Element confirmation flow) and POSTs only the resulting token or PaymentIntent/PaymentMethod ID to the first-party endpoint.
    - Treat any third-party `<script src="...">` loaded on a payment-collection page without an `integrity` attribute (Subresource Integrity, e.g. a `sha256-` hash) as a finding — cite PCI-DSS v4 requirement 6.4.3 (script inventory, script authorization, and integrity control) as the standard-based grounding. Do not accept "we trust this vendor" as a substitute for a visible integrity attribute or equivalent cryptographic verification.
    - Stripe's hosted fields (`CardNumberElement`, `CardExpiryElement`, `CardCVCElement`, `PaymentElement`) render inside sandboxed iframes controlled by Stripe, not merchant code — the browser's same-origin policy and the iframe sandbox boundary are what keep merchant JavaScript from ever reading raw PAN/CVV values. A missing-iframe-isolation finding applies when card fields are collected via manual `<input>` elements with no sandboxed hosted-field equivalent in use.
    - When reviewing broader payment-page change-detection posture, note PCI-DSS v4 requirement 11.6.1 (automated change-detection/alerting for unauthorized script modification on payment pages) as a standard-based expectation — but do not fabricate a finding about a change-detection mechanism you cannot observe in the frontend code under review; note it as an open question instead.
    - Never execute, build, or run application code, and never send live requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
    - Load only the reference needed for the concern in scope.
    
    ## References
    
    Load these only when needed:
    
    - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the defect decision tree, and the required output shape.
    - [Hosted fields and tokenization boundary](references/hosted-fields-and-tokenization.md) — load when reviewing card-entry form markup, `raw-pan-input`, `card-data-persistence`, or `self-posted-card-data` defect classes.
    - [Script integrity and SAQ-A scope reduction](references/script-integrity-and-scope.md) — load when reviewing third-party script tags for Subresource Integrity, or assessing whether a page's use of hosted fields plausibly supports SAQ-A scope-reduction claims.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the payment-collection page(s), card-entry form markup, script tags, and/or client-side persistence/network calls in scope,
    - ranked findings with file:line evidence, defect category (`raw-pan-input`, `card-data-persistence`, `self-posted-card-data`, `unsigned-third-party-script`, or `missing-iframe-isolation`), the concrete data-flow trace (the raw `<input>`/`.value` read, the `localStorage`/analytics call, the POST body construction, or the missing `integrity` attribute), and a fix sketch matching Stripe's documented idiom (`CardNumberElement`, `PaymentElement`, `stripe.createToken`, or an `integrity`/SRI attribute),
    - for every finding involving PAN/CVV/cardholder data exposure, an explicit statement of whether the value stays inside a Stripe-controlled iframe (safe) or is reachable by merchant JavaScript (unsafe) — never approve on the assumption isolation exists without tracing it,
    - evidence level per finding (`repo evidence`, `documentation-based`, `standard-based`, or `inference`), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited or confirmed non-compliant,
    - verdict (approve / approve-with-notes / block),
    - open questions or scope the review could not cover — explicitly restate that this is **not a full PCI-DSS audit**: server-side cardholder-data-environment segmentation, key management, network controls, and non-script requirement families are out of scope and require a dedicated compliance/infrastructure review.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related