Skip to content

Security: olatechy0/Credence-Frontend

Security

docs/SECURITY.md

Content Security Policy

Credence Frontend applies a Content-Security-Policy (CSP) header as a defence-in-depth layer against cross-site scripting (XSS) and data injection attacks. The policy is enforced during development via the Vite dev server and must be replicated in the production hosting layer (see Production Deployment).


Policy

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' ws://localhost:*; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Directive Value Rationale
default-src 'self' Baseline — all resources restricted to the same origin unless overridden.
script-src 'self' Scripts from own origin only. Vite bundles all JS into hashed files; no inline scripts are used in production.
style-src 'self' 'unsafe-inline' Required by Vite's CSS-module injection and React's inline styles. 'unsafe-inline' is the minimum concession needed for SPA rendering.
img-src 'self' data: Same-origin images plus data URIs (used for inline icons / placeholders).
font-src 'self' Font assets served from the same origin.
connect-src 'self' ws://localhost:* API calls to the same origin, plus WebSocket connections for Vite HMR in development.
base-uri 'self' Prevents attackers from injecting <base> tags to hijack relative URLs.
form-action 'self' Restricts form submissions to the same origin.
frame-ancestors 'none' Precludes clickjacking by forbidding the app from being embedded in <frame>, <iframe>, or <object>.

Threat Model

A lax or absent CSP allows an attacker who achieves script injection (e.g. through a compromised dependency, unescaped user input, or a DOM-based XSS gadget) to:

  • Exfiltrate authentication tokens, wallet addresses, or session data to an attacker-controlled endpoint.
  • Modify the DOM to phish user secrets (fake wallet-connection prompts, credential harvesters).
  • Load and execute arbitrary third-party scripts.

The tightened script-src 'self' directive raises the bar from "any script runs" to "only scripts signed by the application's own build artefact run." This is a defence-in-depth measure — it does not replace input sanitisation or output encoding, but it contains the blast radius of a bypass in those layers.


Key Risk: Over-restriction

A CSP that is too strict will break the application:

  • Do not remove 'unsafe-inline' from style-src — Vite and React inject inline <style> elements for CSS modules and component styles. Without it all styling collapses.
  • Do not add 'unsafe-inline' to script-src — this would defeat the purpose of the policy. Vite does not emit inline scripts in production builds.
  • Do not remove ws://localhost:* from connect-src without also adjusting the dev-server port — Vite HMR uses a WebSocket connection in development.

In production, the ws://localhost:* entry is harmless because the origin restriction still applies (the browser will only connect WebSockets to localhost).


Source of Truth

The CSP string is defined in src/config/security.ts and imported by vite.config.ts. Always modify the policy in security.ts — it is the single source of truth and is exercised by the unit test at src/config/security.test.ts.


Production Deployment

The CSP header set by vite.config.ts only applies to the Vite dev server (npm run dev). For production, configure your web server or hosting platform to send the same Content-Security-Policy header with the static build output (dist/).

For backend cookie-signing secrets (session and CSRF), see the Cookie-Secret Rotation Runbook.

nginx example

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'";

Note: ws://localhost:* is omitted from the production configuration because Vite HMR is not present in production builds.


Testing

A negative test in src/config/security.test.ts verifies that:

  • script-src 'self' is present.
  • 'unsafe-eval' and 'unsafe-inline' are not present in script-src.
  • style-src 'self' 'unsafe-inline' is present.
  • The policy does not contain a dangling or malformed directive.

The test would fail without the CSP configuration in place.


Subresource Integrity (SRI)

Threat model

If a <script src> or <link href> loads a resource from a CDN or any cross-origin host, a compromised CDN can silently swap that resource for malicious code. The browser executes or applies whatever bytes it receives with no indication that they differ from the original. For a wallet-adjacent application this means an attacker could:

  • Exfiltrate private keys, seed phrases, or session tokens entered by the user.
  • Replace the wallet-connect UI with a phishing variant that harvests credentials.
  • Inject transaction-manipulation logic that operates silently in the background.

SRI (integrity="sha384-…" crossorigin="anonymous") instructs the browser to hash the received bytes and abort loading if the digest does not match the expected value. The crossorigin attribute is required alongside integrity because it forces the browser to make a CORS request, enabling inspection of the response bytes.

Rule

Every <script src> or <link href> that loads from a cross-origin URL must carry both integrity and crossorigin attributes. Same-origin (relative) assets do not require SRI because they are served directly by the application's own infrastructure.

<!-- ✅ Correct — CDN asset with SRI hash -->
<script
  src="https://cdn.jsdelivr.net/npm/some-lib@1.0/dist/lib.min.js"
  integrity="sha384-<hash>"
  crossorigin="anonymous"
></script>

<!-- ❌ Wrong — CDN asset without SRI -->
<script src="https://cdn.jsdelivr.net/npm/some-lib@1.0/dist/lib.min.js"></script>

Generating an SRI hash

# For a URL:
curl -sL https://cdn.jsdelivr.net/npm/some-lib@1.0/dist/lib.min.js \
  | openssl dgst -sha384 -binary | openssl base64 -A | xargs -I{} echo "sha384-{}"

# Or use https://www.srihash.org

Automated check

src/lib/sriCheck.ts provides a checkSri(htmlSource) function that parses any HTML string and returns a typed SriResult:

import { checkSri } from '../lib/sriCheck'

const result = checkSri(htmlSource)
if (!result.ok) {
  // result.violations: SriViolation[]
  // Each violation has: kind ('missing-integrity' | 'missing-crossorigin' | 'missing-both')
  //                     asset.tag, asset.url, message
}

The unit tests in src/lib/sriCheck.test.ts include:

  • Negative tests — verify that CDN assets without integrity/crossorigin are flagged (these tests demonstrate the exact vulnerability).
  • Positive tests — verify that compliant assets pass.
  • Regression test — reads the actual index.html at the project root and asserts it contains no violations. This test will fail in CI if a future PR adds a CDN tag without an SRI hash.

There aren't any published security advisories