{"slug":"vue-state-store-security-review","title":"vue-state-store-security-review","summary":"Statically review Pinia and legacy Vuex state stores for sensitive data persisted to localStorage/sessionStorage without scoping, untrusted server-payload hydration (window.__pinia/__INITIAL_STATE__) with un-escaped state serialization, SSR store-singleton cross-request pollution","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-05T21:52:18.483271Z","repo":{"url":"https://github.com/VincentChuWaiChow/vanguard-frontier-agentic","stars":24,"forks":3,"license":"Apache-2.0","updatedAt":"2026-10-05T13:00:24Z"},"bodyHtml":"<hr>\n<h2>name: vue-state-store-security-review\ndescription: Statically review Pinia and legacy Vuex state stores for sensitive data persisted to localStorage/sessionStorage without scoping, untrusted server-payload hydration (window.__pinia/<strong>INITIAL_STATE</strong>) with un-escaped state serialization, SSR store-singleton cross-request pollution, store plugins/$subscribe/$onAction acting on untrusted payloads, client-held role flags used as an authorization source of truth, and devtools state exposure in production builds.\nallowed-tools: Read Grep Glob\nmetadata:\nauthor: \"github: VincentChuWaiChow\"\nversion: \"0.1.0\"\nupdated: \"2026-07-03\"\ncategory: security</h2>\n<h1>Vue State Store Security Review</h1>\n<h2>Purpose</h2>\n<p>Review Pinia and legacy Vuex state-store code for the security-critical defect classes that\nare specific to <em>state stores</em> as a component — not general Vue architecture, not composable\ndesign, and not SSR hydration/injection issues that belong to <code>vue-ssr-security-review</code>. This\nskill exists to keep the review anchored to five documented defect classes: (a) sensitive data\npersisted client-side without scoping, (b) store hydration from an untrusted server payload\nwith un-escaped serialization, (c) SSR store-singleton cross-request pollution, (d) store\nplugins/hooks acting on untrusted payloads and client-held flags used as an authorization\nsource of truth, and (e) devtools state exposure in production. It does not re-litigate\ngeneral reactivity/composable architecture, v-html/URL-injection review (covered by\n<code>vue-ssr-security-review</code>), or Composition API design quality (covered by\n<code>vue-composition-api-architecture-review</code>) in every response.</p>\n<h2>When to use</h2>\n<p>Use this skill when the user asks to:</p>\n<ul>\n<li>review a Pinia store definition (<code>defineStore</code>) or a legacy Vuex module/store for security\nissues,</li>\n<li>assess whether <code>pinia-plugin-persistedstate</code> / <code>vuex-persistedstate</code> configuration is safe\n(what gets persisted, and where),</li>\n<li>review SSR store creation/hydration code (<code>entry-server.js</code>, a Nuxt server plugin, an\nExpress/Node handler that constructs <code>createPinia()</code>/<code>createStore()</code>) for cross-request\npollution risk,</li>\n<li>review a store plugin, <code>$subscribe</code>/<code>$onAction</code> hook, or Vuex plugin that consumes\nmutation/action payloads,</li>\n<li>investigate whether a client-side role/permission flag (<code>isAdmin</code>, <code>role</code>) is being trusted\nas an authorization boundary,</li>\n<li>perform a pre-launch security review of a Pinia/Vuex-based application's state layer.</li>\n</ul>\n<p>Do not use this skill for:</p>\n<ul>\n<li><code>v-html</code>/dynamic-URL injection review or SSR entry-point app/router creation — use\n<code>vue-ssr-security-review</code> for those; this skill covers the <em>store</em> specifically, not the\nbroader SSR rendering surface (load both skills together if the review spans both),</li>\n<li>Composition API/composable architecture quality with no security angle — use\n<code>vue-composition-api-architecture-review</code> instead,</li>\n<li>a general \"which state-management library should we pick\" architecture decision with no\nsecurity defect in scope — use <code>state-management-decision-review</code> instead,</li>\n<li>a bug that requires live traffic reproduction (concurrent-request capture, a devtools\nscreen-recording of production, live token exfiltration) to confirm exploitation — static\nanalysis proves the structural risk, not that it has already been exploited.</li>\n</ul>\n<h2>Context7 Documentation Protocol</h2>\n<ul>\n<li>Resolve library IDs with <code>resolve-library-id</code> before citing any store-behavior claim. This\nskill's three grounding libraries: <code>/vuejs/pinia</code> (Pinia core — SSR hydration, plugins,\n<code>$subscribe</code>/<code>$onAction</code>), <code>/prazdevs/pinia-plugin-persistedstate</code> (persistence defaults and\n<code>pick</code>/<code>storage</code> config), <code>/vuejs/vuex</code> (legacy Vuex — <code>state</code> as function, <code>devtools</code>\noption, plugin/module API).</li>\n<li>Use <code>query-docs</code> against <code>/prazdevs/pinia-plugin-persistedstate</code> to confirm persistence\ndefaults before flagging a persistence config: <code>storage</code> defaults to <code>localStorage</code> when\nunset; <code>pick</code> (an array of dotted state-path strings) restricts persistence to named paths;\nwith no <code>pick</code>, the entire state is persisted. Cite these as <code>documentation-based</code>.</li>\n<li>Use <code>query-docs</code> against <code>/vuejs/pinia</code> to confirm SSR hydration mechanics before flagging a\nhydration finding: the documented client-side pattern is\n<code>pinia.state.value = JSON.parse(window.__pinia)</code>, and Pinia's own SSR guide states escaping\nthe serialized state is \"<strong>VERY important</strong> if the content of the state can be changed by\nthe user, which is almost always the case,\" recommending <code>devalue</code> (or equivalent) over naive\n<code>JSON.stringify</code>. Cite as <code>documentation-based</code>.</li>\n<li>Use <code>query-docs</code> against <code>/vuejs/pinia</code> to confirm <code>$subscribe</code>/<code>$onAction</code> hook signatures\n(mutation object with <code>type</code>/<code>storeId</code>/<code>payload</code>; action object with <code>name</code>/<code>store</code>/<code>args</code>/\n<code>after</code>/<code>onError</code>) before describing a plugin-hook finding — do not invent a hook name or\nargument Context7 does not confirm.</li>\n<li>Use <code>query-docs</code> against <code>/vuejs/vuex</code> to confirm the <code>state: () =&gt; ({...})</code> factory pattern\n(module reusability without shared state) and the <code>devtools: boolean</code> store option before\nciting either — do not assume Pinia has an equivalent <code>devtools</code> boolean on <code>defineStore</code>/\n<code>createPinia</code>; Context7 does not confirm that API surface for Pinia, so any Pinia-devtools\nconcern must be scoped to \"a build-time devtools plugin explicitly force-enabled in\nproduction,\" not a Pinia store option, and labeled <code>inference</code> unless a repo-specific config\nis found.</li>\n<li>Read <code>package.json</code> first to confirm which store library is in play (<code>pinia</code>,\n<code>pinia-plugin-persistedstate</code>, <code>vuex</code>, <code>vuex-persistedstate</code>) and its major version — API\nnames and defaults differ between Pinia and Vuex and across Vuex 3/4; do not apply one\nlibrary's API names to the other.</li>\n<li>If Context7 is unavailable, fall back to the <code>official_docs</code> URLs in this skill's\n<code>metadata.json</code> and label the claim <code>documentation-based, unverified against current release</code>.</li>\n</ul>\n<h2>Lean operating rules</h2>\n<ul>\n<li>Findings in every defect class below default to HIGH severity: unscoped sensitive-data\npersistence, untrusted/un-escaped state hydration, SSR store-singleton pollution, untrusted\npayload handling in store plugins/hooks, client-side-flag-as-authorization, and production\ndevtools exposure. Do not downgrade a structural finding to MEDIUM because it has not been\nobserved exploited — the risk is in the structure.</li>\n<li>Trace every finding to a concrete file:line and a concrete data-flow path. \"This store might\nleak sensitive data\" or \"this hydration looks risky\" without naming the specific\n<code>persist</code>/<code>pick</code>/<code>storage</code> config, the specific module-scope <code>createPinia()</code>/<code>createStore()</code>\ndeclaration, or the specific untraced payload sink is not a valid finding — it is a guess.</li>\n<li>Before flagging a persistence config, read the full <code>state()</code> shape and the full <code>persist</code>\nconfig together. A <code>persist: true</code> (or <code>persist: {}</code>) with no <code>pick</code>/<code>paths</code> array persists\nthe entire state — treat every sensitive field in that state as persisted unless a <code>pick</code>\nlist demonstrably excludes it. A <code>pick</code> list that omits the sensitive field(s) clears the\nfinding for those fields specifically (not for the whole store, if other sensitive fields\nremain unscoped).</li>\n<li>Before flagging SSR store creation as cross-request pollution, confirm mutability/reachability\nthe same way <code>vue-ssr-security-review</code> requires for app instances: is the <code>createPinia()</code>/\n<code>createStore()</code> call inside the per-request handler, and does that handler close over any\nmodule-scope mutable/reactive reference? An immutable module-scope constant (a frozen config\nobject, a static route table) is not the risk; a store instance or mutable cache is.</li>\n<li>Do not approve a client-side role/permission flag (<code>isAdmin</code>, <code>role</code>, <code>permissions</code>) as an\nauthorization boundary for a mutating action unless the review also confirms (via visible\nserver-side code, or an explicit statement that server-side authorization is out of scope for\nthis static review) that the actual mutation is re-checked server-side. A store flag gating\nonly UI visibility (hiding a button) is not itself a finding; a store flag gating whether a\nmutating network call is <em>made</em> is a finding regardless of UI-layer intent, because the flag\nis client-writable.</li>\n<li>Do not treat every <code>$subscribe</code>/<code>$onAction</code>/Vuex-plugin hook as risky. Only hooks that act on\nthe payload with a side effect (write, external call, log of sensitive data, mutate another\nstore) and lack payload validation are findings; read-only observation (e.g., analytics event\nnaming) is not.</li>\n<li>Never execute, build, or run application code, and never send live requests, as part of this\nreview; this is a static-review skill (Read/Grep/Glob only).</li>\n<li>Load only the reference needed for the concern in scope.</li>\n</ul>\n<h2>References</h2>\n<p>Load these only when needed:</p>\n<ul>\n<li><a href=\"references/workflow-and-output.md\">Review workflow and findings contract</a> — use for the\nstep-by-step review procedure, the decision tree across all five defect classes, and the\nrequired output shape.</li>\n<li><a href=\"references/persistence-and-hydration.md\">Client-side persistence and hydration</a> — load when\nreviewing <code>pinia-plugin-persistedstate</code>/<code>vuex-persistedstate</code> configuration, SSR store\ncreation/singleton risk, or server-payload hydration (<code>window.__pinia</code>/<code>__INITIAL_STATE__</code>).</li>\n<li><a href=\"references/untrusted-payloads-and-authorization.md\">Untrusted payloads and authorization</a> —\nload when reviewing <code>$subscribe</code>/<code>$onAction</code>/Vuex-plugin hooks, client-held role/permission\nflags used for access control, or devtools production exposure.</li>\n<li><a href=\"references/acceptance-rubric.md\">Acceptance rubric</a> — the enumerated defect/false-positive\nlist this skill's rules were authored against; load if you need the underlying catch list\nrather than the operating rules derived from it.</li>\n</ul>\n<h2>Response minimum</h2>\n<p>Return, at minimum:</p>\n<ul>\n<li>the store definition(s), persistence config, SSR entry point(s), and/or plugin/hook code in\nscope,</li>\n<li>ranked findings with file:line evidence, defect category (<code>persistence</code>, <code>hydration</code>,\n<code>ssr-pollution</code>, <code>untrusted-payload</code>, <code>client-auth-flag</code>, or <code>devtools-exposure</code>), the\nconcrete data-flow trace (state field → persist config, or server payload → hydration call,\nor module-scope declaration → per-request reachability, or payload → sink), and a fix sketch\nmatching the grounding library's documented pattern,</li>\n<li>for every persistence finding, an explicit statement of which state fields are covered by\n<code>pick</code>/<code>paths</code> (if any) and which are not,</li>\n<li>for every client-side-flag finding, an explicit statement of whether a server-side\nauthorization re-check was found, not found, or is out of scope for this static review,</li>\n<li>evidence level per finding (<code>repo evidence</code>, <code>documentation-based</code>, or <code>inference</code>), with\nstructural risk findings explicitly labeled as structural risk, not as confirmed-exploited,</li>\n<li>verdict (approve / approve-with-notes / block),</li>\n<li>open questions or scope the review could not cover (e.g., \"confirming actual cross-request\nleakage requires concurrent-request load testing,\" or \"v-html/URL-injection review of this\nsame app is out of scope for this skill — see <code>vue-ssr-security-review</code>\").</li>\n</ul>\n","files":[{"path":"metadata.json","sizeBytes":2368,"isText":true},{"path":"references/acceptance-rubric.md","sizeBytes":6889,"isText":true},{"path":"references/persistence-and-hydration.md","sizeBytes":11301,"isText":true},{"path":"references/untrusted-payloads-and-authorization.md","sizeBytes":10052,"isText":true},{"path":"references/workflow-and-output.md","sizeBytes":10022,"isText":true},{"path":"SKILL.md","sizeBytes":11285,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-05T21:59:15.742581Z","sha256":"60EA8EED583243D86587E4587FDE76D6D72FA62E5FC659951CCCAAB2642D80F7","sizeBytes":21610},"review":null,"source":{"repositoryUrl":"https://github.com/VincentChuWaiChow/vanguard-frontier-agentic","path":"skills/frontend/vue-state-store-security-review","license":"Apache-2.0","commit":"febe32a08e78fd06b1e466187410d673f1958d87","subtreeSha":"E5EF8257018B304464FC940B0C136D84D3C49016ADBD00D559500BCD041DB74D","lastSyncedAt":"2026-10-05T21:51:58.639905Z"},"reviewedAt":"2026-10-05T22:13:37.735256Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/vue-state-store-security-review"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart"},{"target":"git","command":"git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git"}]}