Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 82 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions src/data/DocumentsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,28 @@ export const getDocuments = async (): Promise<Document[]> => {
{
label: 'Default',
url: {
'W3C VC': docs.coo_default.qrCode.uri,
'W3C VC': decodeURIComponent(docs.coo_default.qrCode.uri),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Harden URI decoding: wrap decodeURIComponent in a safe helper and reuse it across all W3C entries

decodeURIComponent throws on malformed percent-escapes (e.g., stray "%"), which would crash rendering if any JSON has an unexpected value. OA uses a guarded decoder already; mirror that for W3C to be robust and DRY.

Apply this diff to replace direct calls with a safe helper:

-            'W3C VC': decodeURIComponent(docs.coo_default.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_default.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_redacted.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_redacted.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_revoked.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_revoked.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_expired.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_expired.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.epn_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.epn_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.epn_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.epn_inoperative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.bol_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.bol_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.bol_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.bol_inoperative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_default.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_default.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_redacted.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_redacted.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_revoked.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_revoked.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_expired.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_expired.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.whr_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.whr_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.whr_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.whr_inoperative.qrCode.uri),

Add this helper near decodeOAString:

const decodeUriSafe = (value: string) => {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
};

Optional verification script to quickly sanity-check repository JSONs for risky percent-escapes that could throw in decodeURIComponent:

Also applies to: 102-102, 109-109, 116-116, 136-136, 143-143, 162-162, 169-169, 189-189, 196-196, 203-203, 210-210, 229-229, 236-236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Heuristic scan for the JSON payloads referenced in this file and inspect qrCode.uri values.
python - << 'PY'
import os, re, json

# Filenames referenced by DocumentsData.ts (W3C ones only)
names = {
  'certificate-of-origin-default.json',
  'certificate-of-origin-redacted.json',
  'certificate-of-origin-revoked.json',
  'certificate-of-origin-expired.json',
  'electronic-promissory-note-operative.json',
  'electronic-promissory-note-inoperative.json',
  'bill-of-lading-operative.json',
  'bill-of-lading-inoperative.json',
  'invoice-default.json',
  'invoice-redacted.json',
  'invoice-revoked.json',
  'invoice-expired.json',
  'warehouse-receipt-operative.json',
  'warehouse-receipt-inoperative.json',
}

def find_files(root='.'):
  for dirpath, _, filenames in os.walk(root):
    for f in filenames:
      if f in names:
        yield os.path.join(dirpath, f)

def find_qr_uris(obj):
  # Recursively find any obj like {"qrCode": {"uri": "<...>"}}
  if isinstance(obj, dict):
    if 'qrCode' in obj and isinstance(obj['qrCode'], dict) and 'uri' in obj['qrCode']:
      val = obj['qrCode']['uri']
      if isinstance(val, str):
        yield val
    for v in obj.values():
      yield from find_qr_uris(v)
  elif isinstance(obj, list):
    for it in obj:
      yield from find_qr_uris(it)

files = list(find_files('.'))
if not files:
  print('No referenced W3C JSON files found. If they’re stored under public/static, ensure they exist on this branch.')
else:
  for p in sorted(files):
    try:
      with open(p, 'r', encoding='utf-8') as fh:
        data = json.load(fh)
    except Exception as e:
      print(f'[PARSE-ERROR] {p}: {e}')
      continue
    uris = list(find_qr_uris(data))
    if not uris:
      print(f'[NO-URI] {p}')
      continue
    for u in uris:
      has_pct = bool(re.search(r'%[0-9A-Fa-f]{2}', u))
      has_bad = bool(re.search(r'%(?![0-9A-Fa-f]{2})', u))
      print(f'[OK] {p} | encoded={has_pct} | potentially_malformed={has_bad} | sample="{u[:120]}"')
PY

Length of output: 4078


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for decodeURIComponent occurrences ==="
rg -n --hidden -S "decodeURIComponent" || true

echo
echo "=== Show src/data/DocumentsData.ts with line numbers (if present) ==="
if [ -f src/data/DocumentsData.ts ]; then
  nl -ba src/data/DocumentsData.ts | sed -n '1,320p'
else
  echo "File src/data/DocumentsData.ts not found"
fi

echo
echo "=== Search for decodeOAString helper ==="
rg -n --hidden -S "decodeOAString" || true

echo
echo "=== Search for existing decodeUriSafe helper (to avoid duplicate) ==="
rg -n --hidden -S "decodeUriSafe" || true

Length of output: 2014


Harden URI decoding: add a guarded decodeUriSafe and use it for all W3C VC entries

decodeURIComponent can throw on malformed percent-escapes (e.g. stray "%") — add a small safe wrapper and replace direct calls to avoid runtime crashes.

  • File: src/data/DocumentsData.ts
    • add the helper near the existing decodeOAString (decodeOAString found at ~line 72)
    • replace direct calls at these lines: 95, 102, 109, 116, 136, 143, 162, 169, 189, 196, 203, 210, 229, 236
    • consider updating decodeOAString's internal decodeURIComponent to use decodeUriSafe as well

Apply this diff to replace direct calls:

-            'W3C VC': decodeURIComponent(docs.coo_default.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_default.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_redacted.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_redacted.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_revoked.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_revoked.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.coo_expired.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.coo_expired.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.epn_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.epn_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.epn_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.epn_inoperative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.bol_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.bol_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.bol_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.bol_inoperative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_default.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_default.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_redacted.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_redacted.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_revoked.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_revoked.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.inv_expired.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.inv_expired.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.whr_operative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.whr_operative.qrCode.uri),

-            'W3C VC': decodeURIComponent(docs.whr_inoperative.qrCode.uri),
+            'W3C VC': decodeUriSafe(docs.whr_inoperative.qrCode.uri),

Add this helper near decodeOAString:

const decodeUriSafe = (value: string) => {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
};

Note: I scanned the repo — decodeURIComponent occurrences exist at the lines above, and the public/static W3C JSONs in this branch show percent-encoded URIs (no malformed percent-escapes found), but runtime protection is still recommended.

🤖 Prompt for AI Agents
In src/data/DocumentsData.ts around line ~72 and affecting lines
95,102,109,116,136,143,162,169,189,196,203,210,229,236: decodeURIComponent is
used directly for W3C VC URIs and can throw on malformed percent-escapes; add a
small safe wrapper function (named decodeUriSafe) near the existing
decodeOAString (around line 72) that returns the original string on decode
errors, replace all direct decodeURIComponent calls at the listed lines with
decodeUriSafe, and update decodeOAString to call decodeUriSafe instead of
decodeURIComponent so all URI decoding is guarded against exceptions.

'OA (Legacy)': decodeOAString(docs.coo_default_oa.data.links.self.href),
},
},
{
label: 'Redacted',
url: {
'W3C VC': docs.coo_redacted.qrCode.uri,
'W3C VC': decodeURIComponent(docs.coo_redacted.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.coo_redacted_oa.data.links.self.href),
},
},
{
label: 'Revoked',
url: {
'W3C VC': docs.coo_revoked.qrCode.uri,
'W3C VC': decodeURIComponent(docs.coo_revoked.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.coo_revoked_oa.data.links.self.href),
},
},
{
label: 'Expired',
url: {
'W3C VC': docs.coo_expired.qrCode.uri,
'W3C VC': decodeURIComponent(docs.coo_expired.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.coo_expired_oa.data.links.self.href),
},
},
Expand All @@ -133,14 +133,14 @@ export const getDocuments = async (): Promise<Document[]> => {
{
label: 'Operative',
url: {
'W3C VC': docs.epn_operative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.epn_operative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.epn_operative_oa.data.links.self.href),
},
},
{
label: 'Inoperative',
url: {
'W3C VC': docs.epn_inoperative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.epn_inoperative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.epn_inoperative_oa.data.links.self.href),
},
},
Expand All @@ -159,14 +159,14 @@ export const getDocuments = async (): Promise<Document[]> => {
{
label: 'Operative',
url: {
'W3C VC': docs.bol_operative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.bol_operative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.bol_operative_oa.data.links.self.href),
},
},
{
label: 'Inoperative',
url: {
'W3C VC': docs.bol_inoperative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.bol_inoperative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.bol_inoperative_oa.data.links.self.href),
},
},
Expand All @@ -186,28 +186,28 @@ export const getDocuments = async (): Promise<Document[]> => {
{
label: 'Default',
url: {
'W3C VC': docs.inv_default.qrCode.uri,
'W3C VC': decodeURIComponent(docs.inv_default.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.inv_default_oa.data.links.self.href),
},
},
{
label: 'Redacted',
url: {
'W3C VC': docs.inv_redacted.qrCode.uri,
'W3C VC': decodeURIComponent(docs.inv_redacted.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.inv_redacted_oa.data.links.self.href),
},
},
{
label: 'Revoked',
url: {
'W3C VC': docs.inv_revoked.qrCode.uri,
'W3C VC': decodeURIComponent(docs.inv_revoked.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.inv_revoked_oa.data.links.self.href),
},
},
{
label: 'Expired',
url: {
'W3C VC': docs.inv_expired.qrCode.uri,
'W3C VC': decodeURIComponent(docs.inv_expired.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.inv_expired_oa.data.links.self.href),
},
},
Expand All @@ -226,14 +226,14 @@ export const getDocuments = async (): Promise<Document[]> => {
{
label: 'Operative',
url: {
'W3C VC': docs.whr_operative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.whr_operative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.whr_operative_oa.data.links.self.href),
},
},
{
label: 'Inoperative',
url: {
'W3C VC': docs.whr_inoperative.qrCode.uri,
'W3C VC': decodeURIComponent(docs.whr_inoperative.qrCode.uri),
'OA (Legacy)': decodeOAString(docs.whr_inoperative_oa.data.links.self.href),
},
},
Expand Down