{
  "_comment": "NoData Guard sample scan output — produced by `npx @nodatachat/guard@latest --project sandbox-shop --emit json`. Findings are real categories from a synthetic e-commerce app. `migration_sql` is paste-ready.",
  "schema": "nodata.guard.scan.v4",
  "scan_id": "scan-sample-shop-2026-04-25",
  "scanned_at": "2026-04-25T08:14:22.001Z",
  "project": {
    "name": "sandbox-shop",
    "id": "proj_sample_shop",
    "engine": "postgres",
    "host_redacted": "*****.supabase.co",
    "tables_total": 24,
    "rows_estimated": 184320
  },
  "summary": {
    "score_a_through_f": "C+",
    "rls_off": 3,
    "rls_on": 21,
    "policies_total": 47,
    "pii_columns_detected": 8,
    "encryptable_columns": 5,
    "high_severity": 2,
    "medium_severity": 6,
    "low_severity": 4
  },
  "findings": [
    {
      "id": "f1",
      "severity": "high",
      "category": "rls_off",
      "table": "customer_profiles",
      "column": null,
      "message": "Row Level Security is not enabled on a table containing customer PII (email, phone, billing_address).",
      "rationale": "Without RLS, any authenticated request bypassing application logic can read every customer row. Service-role and anon-role become equally permissive at the DB layer.",
      "ack_state": "open",
      "migration_sql": "ALTER TABLE customer_profiles ENABLE ROW LEVEL SECURITY;\nCREATE POLICY customer_profiles_owner_read ON customer_profiles\n  FOR SELECT TO authenticated\n  USING (auth.uid() = user_id);\nCREATE POLICY customer_profiles_owner_write ON customer_profiles\n  FOR UPDATE TO authenticated\n  USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);"
    },
    {
      "id": "f2",
      "severity": "high",
      "category": "plaintext_pii",
      "table": "customer_profiles",
      "column": "phone",
      "message": "Plaintext phone numbers detected in 12,840 rows. Encryption recommended.",
      "rationale": "Phone is a re-identification vector under GDPR Recital 26. AES-256-GCM column-level encryption with sidecar HMAC chain converts this from regulated PII to operational metadata.",
      "ack_state": "open",
      "migration_sql": "-- Step 1: add ciphertext column (sidecar pattern)\nALTER TABLE customer_profiles ADD COLUMN phone_ct TEXT;\nALTER TABLE customer_profiles ADD COLUMN phone_hash TEXT GENERATED ALWAYS AS (encode(digest(phone, 'sha256'), 'hex')) STORED;\n-- Step 2: backfill via NoData Protect (run from app):\n-- await nd.encrypt('customer_profiles.phone');\n-- Step 3: drop plaintext after verification\n-- ALTER TABLE customer_profiles DROP COLUMN phone;"
    },
    {
      "id": "f3",
      "severity": "medium",
      "category": "rls_policy_always_true",
      "table": "orders",
      "column": null,
      "message": "RLS policy `orders_authenticated_full_access` uses USING (true) — equivalent to no policy.",
      "rationale": "A USING-true policy passes the RLS check for every row. This is a known anti-pattern; usually a leftover from initial development.",
      "ack_state": "open",
      "migration_sql": "DROP POLICY orders_authenticated_full_access ON orders;\nCREATE POLICY orders_owner_read ON orders\n  FOR SELECT TO authenticated\n  USING (auth.uid() = customer_user_id);"
    },
    {
      "id": "f4",
      "severity": "medium",
      "category": "missing_chain_attestation",
      "table": "orders",
      "column": null,
      "message": "Order writes are not attested to NoData chain. Disputes cannot be resolved with cryptographic proof.",
      "rationale": "Without chain receipts, a customer claiming `I never placed this order` has no provable counter-evidence. With chain attestation, every order write produces a tamper-evident receipt.",
      "ack_state": "open",
      "migration_sql": "-- Application-side: wrap order INSERT in nd.attest()\n-- See: https://www.npmjs.com/package/@nodatachat/protect#attest"
    },
    {
      "id": "f5",
      "severity": "low",
      "category": "function_search_path_mutable",
      "table": null,
      "column": null,
      "message": "8 SECURITY DEFINER functions have mutable search_path.",
      "rationale": "A mutable search_path can be exploited by a low-privileged user to inject schema-shadowing objects. Setting `SET search_path = pg_catalog, public` mitigates.",
      "ack_state": "open",
      "migration_sql": "-- Apply per function:\nALTER FUNCTION public.calculate_total(int) SET search_path = pg_catalog, public;\n-- Repeat for: handle_order_insert, refresh_inventory, etc."
    }
  ],
  "metadata": {
    "scanner_version": "@nodatachat/guard@4.3.2",
    "guard_signature": "sha256:086fb29f1d7eaf2c4b9c3a8e5d1f6072c4a89b3e2f7d09e1a4b6c8d3f5a7b9c1",
    "scan_duration_ms": 1842,
    "loop_url": "https://www.nodatacapsule.com/my-capsule/scan/scan-sample-shop-2026-04-25"
  }
}
