@react-vault/create-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +16 -0
- package/bin/create-app.js +8 -0
- package/claude-toolkit/README.md +131 -0
- package/claude-toolkit/agents/bfsi-accessibility-auditor.md +132 -0
- package/claude-toolkit/agents/bfsi-architect.md +156 -0
- package/claude-toolkit/agents/bfsi-code-reviewer.md +137 -0
- package/claude-toolkit/agents/bfsi-compliance-auditor.md +161 -0
- package/claude-toolkit/agents/bfsi-pii-scanner.md +142 -0
- package/claude-toolkit/agents/bfsi-pr-reviewer.md +114 -0
- package/claude-toolkit/agents/bfsi-security-reviewer.md +136 -0
- package/claude-toolkit/commands/bfsi-audit.md +46 -0
- package/claude-toolkit/commands/bfsi-doctor.md +97 -0
- package/claude-toolkit/commands/bfsi-review.md +46 -0
- package/claude-toolkit/commands/bfsi-scaffold.md +47 -0
- package/claude-toolkit/hooks/hooks.json +181 -0
- package/claude-toolkit/hooks/scripts/a11y-check.sh +63 -0
- package/claude-toolkit/hooks/scripts/audit-prompt.sh +36 -0
- package/claude-toolkit/hooks/scripts/block-destructive.sh +41 -0
- package/claude-toolkit/hooks/scripts/block-force-push.sh +30 -0
- package/claude-toolkit/hooks/scripts/format.sh +42 -0
- package/claude-toolkit/hooks/scripts/inject-context.sh +44 -0
- package/claude-toolkit/hooks/scripts/lint.sh +45 -0
- package/claude-toolkit/hooks/scripts/protect-files.sh +53 -0
- package/claude-toolkit/hooks/scripts/save-compliance-context.sh +35 -0
- package/claude-toolkit/hooks/scripts/scan-pii.sh +87 -0
- package/claude-toolkit/hooks/scripts/scan-secrets.sh +67 -0
- package/claude-toolkit/hooks/scripts/verify-clean.sh +50 -0
- package/claude-toolkit/package.json +22 -0
- package/claude-toolkit/plugin.json +31 -0
- package/claude-toolkit/skills/bfsi-api-endpoint/SKILL.md +105 -0
- package/claude-toolkit/skills/bfsi-commit/SKILL.md +102 -0
- package/claude-toolkit/skills/bfsi-compliance-check/SKILL.md +107 -0
- package/claude-toolkit/skills/bfsi-encrypt-helper/SKILL.md +127 -0
- package/claude-toolkit/skills/bfsi-error-message/SKILL.md +162 -0
- package/claude-toolkit/skills/bfsi-feature/SKILL.md +120 -0
- package/claude-toolkit/skills/bfsi-feature/references/architecture.md +69 -0
- package/claude-toolkit/skills/bfsi-feature/references/audit-events.md +70 -0
- package/claude-toolkit/skills/bfsi-feature/scripts/scaffold.mjs +136 -0
- package/claude-toolkit/skills/bfsi-form/SKILL.md +73 -0
- package/claude-toolkit/skills/bfsi-form/references/validation-regex.md +50 -0
- package/claude-toolkit/skills/bfsi-onboarding/SKILL.md +110 -0
- package/claude-toolkit/skills/bfsi-pii-field/SKILL.md +90 -0
- package/claude-toolkit/skills/bfsi-test-pattern/SKILL.md +179 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +339 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/templates/_shared/.claude/settings.json +31 -0
- package/templates/_shared/.env.local.sample +25 -0
- package/templates/_shared/.github/workflows/ci.yml +49 -0
- package/templates/_shared/CLAUDE.md +89 -0
- package/templates/_shared/README.md +50 -0
- package/templates/_shared/index.html +16 -0
- package/templates/_shared/package.json +73 -0
- package/templates/_shared/postcss.config.cjs +6 -0
- package/templates/_shared/src/app/App.tsx +13 -0
- package/templates/_shared/src/app/globals.css +64 -0
- package/templates/_shared/src/env.ts +33 -0
- package/templates/_shared/src/i18n/i18n.ts +18 -0
- package/templates/_shared/src/i18n/translations/en.json +54 -0
- package/templates/_shared/src/i18n/translations/hi.json +30 -0
- package/templates/_shared/src/main.tsx +16 -0
- package/templates/_shared/src/routes/ProtectedRoute.tsx +28 -0
- package/templates/_shared/src/routes/index.tsx +67 -0
- package/templates/_shared/src/shared/ErrorBoundary.tsx +60 -0
- package/templates/_shared/tailwind.config.ts +68 -0
- package/templates/_shared/tests/setup.ts +7 -0
- package/templates/_shared/tsconfig.json +33 -0
- package/templates/_shared/tsconfig.node.json +13 -0
- package/templates/_shared/vite.config.ts +47 -0
- package/templates/rtk-query/.claude/skills/axios-auth/SKILL.md +103 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/error-shape.md +84 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +146 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/notification-wiring.md +141 -0
- package/templates/rtk-query/.claude/skills/constants-organization/SKILL.md +112 -0
- package/templates/rtk-query/.claude/skills/constants-organization/references/example-files.md +134 -0
- package/templates/rtk-query/.claude/skills/constants-organization/references/tag-types-catalog.md +53 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/SKILL.md +159 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/references/localStorage-persistence.md +70 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/references/middleware-patterns.md +82 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/SKILL.md +148 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/cache-strategies.md +96 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/endpoint-cookbook.md +145 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/optimistic-update.md +53 -0
- package/templates/rtk-query/README.md +84 -0
- package/templates/rtk-query/package.partial.json +7 -0
- package/templates/rtk-query/src/app/App.tsx +23 -0
- package/templates/rtk-query/src/axiosconfig/axiosInstance.ts +26 -0
- package/templates/rtk-query/src/axiosconfig/baseQuery.ts +72 -0
- package/templates/rtk-query/src/axiosconfig/interceptor.ts +42 -0
- package/templates/rtk-query/src/redux/invalidateCacheMiddleware.ts +20 -0
- package/templates/rtk-query/src/redux/reduxHooks.ts +10 -0
- package/templates/rtk-query/src/redux/rootReducer.ts +18 -0
- package/templates/rtk-query/src/redux/store.ts +36 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/SKILL.md +109 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/error-shape.md +89 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +121 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/notification-pattern.md +109 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/SKILL.md +144 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/references/example-files.md +111 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/references/query-key-factories.md +129 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/SKILL.md +165 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/references/devtools.md +67 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/references/global-handlers.md +94 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/SKILL.md +142 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/audited-mutation.md +144 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/optimistic-update.md +102 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/service-cookbook.md +151 -0
- package/templates/tanstack-query/README.md +63 -0
- package/templates/tanstack-query/package.partial.json +8 -0
- package/templates/tanstack-query/src/api/axiosInstance.ts +20 -0
- package/templates/tanstack-query/src/api/http.ts +62 -0
- package/templates/tanstack-query/src/api/queryClient.ts +28 -0
- package/templates/tanstack-query/src/app/App.tsx +20 -0
- package/templates/tanstack-query/src/services/example.ts +32 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bfsi-compliance-check
|
|
3
|
+
description: Runs an OWASP Top 10 + RBI + PCI-DSS + IRDAI + SOC2 compliance checklist against the current branch's diff. Reports findings grouped by severity (critical, high, medium, low) with file:line references and remediation steps. Use when the user types /bfsi-compliance-check, asks to "run compliance check", "audit my changes for compliance", or "check this PR for security issues".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
allowed-tools: Read Grep Glob Bash(git diff:*) Bash(git log:*)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# BFSI Compliance Check
|
|
9
|
+
|
|
10
|
+
Static compliance scan over the current branch's diff (`git diff origin/main...HEAD`).
|
|
11
|
+
|
|
12
|
+
## Checks performed
|
|
13
|
+
|
|
14
|
+
### Critical (must fix before merge)
|
|
15
|
+
|
|
16
|
+
1. **Hardcoded secrets** — API keys, tokens, passwords, connection strings in code/configs
|
|
17
|
+
- Pattern: `api_key|secret|password|token|private_key` followed by `= ?['"][^'"]+['"]`
|
|
18
|
+
- Action: extract to env var
|
|
19
|
+
2. **Card data in non-PCI iframe** — `<input>` capturing card number/CVV outside `<PCITokenizedCardInput>`
|
|
20
|
+
- Pattern: `name="card_number"|name="cvv"|name="cvc"` not inside `PCITokenized*`
|
|
21
|
+
3. **PII in console.log / console.error** — `console.log(user.pan)`, etc.
|
|
22
|
+
- Pattern: `console\.(log|info|warn|error).*\.(pan|aadhaar|account|password|cvv|otp)`
|
|
23
|
+
4. **localStorage with PII** — `localStorage.setItem(..., user.pan)`, etc.
|
|
24
|
+
- Pattern: `localStorage\.setItem.*\.(pan|aadhaar|account|password)`
|
|
25
|
+
5. **Unencrypted IndexedDB** — direct `idb.put()` of objects containing PII fields
|
|
26
|
+
- Suggest using `secureStorage` from `@react-vault/core/storage`
|
|
27
|
+
6. **Missing CSRF token on mutation** — `fetch('/api/...', { method: 'POST', ... })` without `X-CSRF-Token` (if not using cookie-less JWT)
|
|
28
|
+
|
|
29
|
+
### High (fix before next sprint)
|
|
30
|
+
|
|
31
|
+
7. **Weak crypto** — `md5`, `sha1`, `Math.random()` for security purposes, `crypto.createCipher` (deprecated)
|
|
32
|
+
8. **`dangerouslySetInnerHTML`** without sanitisation
|
|
33
|
+
9. **Missing `<ProtectedRoute>`** on a route that fetches user data
|
|
34
|
+
10. **Missing `permission` prop** on `<ProtectedRoute>` (defaults to authenticated-only)
|
|
35
|
+
11. **API mutation without audit hook** — `useMutation` instead of `useAuditedMutation`
|
|
36
|
+
12. **Missing Zod parse on API response** — `query` without `transformResponse: z.parse(...)`
|
|
37
|
+
13. **`autocomplete="on"` on PII input field**
|
|
38
|
+
14. **Form submit without idempotency-key header**
|
|
39
|
+
|
|
40
|
+
### Medium (track for hardening)
|
|
41
|
+
|
|
42
|
+
15. **Inline event handlers in JSX** for sensitive actions (harder to audit)
|
|
43
|
+
16. **Untranslated user-facing strings** (no `t()` wrap)
|
|
44
|
+
17. **Missing `aria-label`** on PII reveal buttons
|
|
45
|
+
18. **Magic numbers** for amounts (use named constants)
|
|
46
|
+
19. **TODO/FIXME** comments mentioning security
|
|
47
|
+
20. **No tests** for files in `src/features/*/api.ts` or containers
|
|
48
|
+
|
|
49
|
+
### Low (best-practice nudges)
|
|
50
|
+
|
|
51
|
+
21. **`React.FC`** usage (prefer named function components for stack traces)
|
|
52
|
+
22. **Default exports** (prefer named exports for refactor safety)
|
|
53
|
+
23. **`as any`** type assertions
|
|
54
|
+
24. **Files over 400 lines**
|
|
55
|
+
|
|
56
|
+
## Workflow
|
|
57
|
+
|
|
58
|
+
### Step 1: Get the diff
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git diff --name-only origin/main...HEAD
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If no diff (or origin/main missing), fall back to `git diff HEAD~5...HEAD`.
|
|
65
|
+
|
|
66
|
+
### Step 2: For each category, run targeted greps
|
|
67
|
+
|
|
68
|
+
Use the patterns above. Be specific — minimise false positives. Each finding records: file, line, category, severity, the offending snippet, suggested fix.
|
|
69
|
+
|
|
70
|
+
### Step 3: Group + report
|
|
71
|
+
|
|
72
|
+
Output as markdown:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
# Compliance Check Report
|
|
76
|
+
|
|
77
|
+
Branch: <name> → origin/main (N files changed)
|
|
78
|
+
|
|
79
|
+
## Critical (must fix): N findings
|
|
80
|
+
1. **Hardcoded secret** in src/api/auth.ts:42
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
const API_KEY = 'sk-abc123...'
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Fix: replace with `import.meta.env.VITE_API_KEY` and add to `.env.local.sample`.
|
|
87
|
+
|
|
88
|
+
## High: N findings
|
|
89
|
+
...
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Step 4: Exit code
|
|
93
|
+
|
|
94
|
+
If any critical findings, end with: "❌ NOT MERGE-READY: N critical findings must be addressed."
|
|
95
|
+
If high but no critical: "⚠️ Mergeable but address N high findings before next sprint."
|
|
96
|
+
Otherwise: "✅ All checks passed."
|
|
97
|
+
|
|
98
|
+
## Note on scope
|
|
99
|
+
|
|
100
|
+
This is a **static** check based on patterns. It catches obvious issues but is not a substitute for:
|
|
101
|
+
|
|
102
|
+
- `bfsi-security-reviewer` agent (which reasons about flows)
|
|
103
|
+
- Backend security review
|
|
104
|
+
- Penetration testing
|
|
105
|
+
- Third-party SAST/DAST tools
|
|
106
|
+
|
|
107
|
+
Always run before requesting human PR review.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bfsi-encrypt-helper
|
|
3
|
+
description: Reference for encrypting and decrypting sensitive data using @react-vault/core/encryption. Covers AES-GCM (symmetric), RSA-OAEP (asymmetric), PBKDF2 (key derivation), and envelope encryption patterns. Auto-loads when the user asks about encrypting data, decrypting data, key derivation, key rotation, Web Crypto API usage, securing sensitive fields, or implementing envelope encryption.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BFSI Encryption Helper
|
|
7
|
+
|
|
8
|
+
Reference for `@react-vault/core/encryption`. Uses the browser's Web Crypto API — no external dependencies, no key material crosses package boundaries unprotected.
|
|
9
|
+
|
|
10
|
+
## Algorithms
|
|
11
|
+
|
|
12
|
+
| Algorithm | Use | Module |
|
|
13
|
+
| --------------- | ----------------------------- | ------------------------------------ |
|
|
14
|
+
| AES-GCM 256 | Symmetric encryption of data | `aesgcm.encrypt`, `aesgcm.decrypt` |
|
|
15
|
+
| RSA-OAEP-SHA256 | Asymmetric encryption of keys | `rsaoaep.encrypt`, `rsaoaep.decrypt` |
|
|
16
|
+
| PBKDF2-SHA256 | Derive key from password | `pbkdf2.deriveKey` |
|
|
17
|
+
| HKDF-SHA256 | Derive key from existing key | `hkdf.deriveKey` |
|
|
18
|
+
| ECDSA-P256 | Sign / verify | `ecdsa.sign`, `ecdsa.verify` |
|
|
19
|
+
|
|
20
|
+
## Common patterns
|
|
21
|
+
|
|
22
|
+
### Encrypt a string with a known symmetric key
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { aesgcm } from '@react-vault/core/encryption';
|
|
26
|
+
|
|
27
|
+
const key = await aesgcm.generateKey();
|
|
28
|
+
const ciphertext = await aesgcm.encrypt(key, 'sensitive value');
|
|
29
|
+
const plaintext = await aesgcm.decrypt(key, ciphertext);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`encrypt` returns a base64 blob containing IV + ciphertext + auth tag. Don't try to compose this yourself — it's not interchangeable with raw Web Crypto output without the framing.
|
|
33
|
+
|
|
34
|
+
### Derive a key from a password
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { pbkdf2 } from '@react-vault/core/encryption';
|
|
38
|
+
|
|
39
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
40
|
+
const key = await pbkdf2.deriveKey(password, salt, { iterations: 600_000 });
|
|
41
|
+
// Use the key for AES-GCM. Store salt alongside ciphertext.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
600k iterations is OWASP 2025 recommendation for PBKDF2-SHA256. Don't lower it.
|
|
45
|
+
|
|
46
|
+
### Envelope encryption (BFSI standard)
|
|
47
|
+
|
|
48
|
+
This is the right pattern for storing many fields with one key, with key rotation:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { envelope } from '@react-vault/core/encryption';
|
|
52
|
+
|
|
53
|
+
// Setup: backend provides a public KEK (key-encrypting-key).
|
|
54
|
+
const masterPublicKey = await rsaoaep.importPublicKey(masterPublicKeyPem);
|
|
55
|
+
|
|
56
|
+
// Encrypt: generate a per-record DEK, encrypt data with DEK,
|
|
57
|
+
// then encrypt DEK with the master KEK.
|
|
58
|
+
const { ciphertext, encryptedDek } = await envelope.encrypt(
|
|
59
|
+
masterPublicKey,
|
|
60
|
+
JSON.stringify({ pan: '...', aadhaar: '...' }),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Store: { ciphertext, encryptedDek } alongside the record.
|
|
64
|
+
// Decrypt: backend decrypts DEK with master private key, sends DEK to client,
|
|
65
|
+
// client decrypts ciphertext with DEK.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Key rotation: rotate the KEK on the backend; re-encrypt only the small `encryptedDek` per record. The large `ciphertext` doesn't need to be touched.
|
|
69
|
+
|
|
70
|
+
## Where NOT to use
|
|
71
|
+
|
|
72
|
+
- **Passwords**: never encrypt — hash with bcrypt/argon2 on the BACKEND. Client-side hashing helps you nothing.
|
|
73
|
+
- **Card numbers**: never encrypt yourself. Use `<PCITokenizedCardInput>`. Real card data should never reach your app.
|
|
74
|
+
- **Auth tokens**: don't encrypt — protect via secure storage (memory-first, see `@react-vault/core/storage`).
|
|
75
|
+
- **Session cookies**: server's job, not yours.
|
|
76
|
+
|
|
77
|
+
## Anti-patterns
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// ❌ Don't roll your own crypto
|
|
81
|
+
const encrypted = btoa(JSON.stringify(data)); // NOT encryption
|
|
82
|
+
|
|
83
|
+
// ❌ Don't use Math.random() for keys / IVs
|
|
84
|
+
const iv = new Uint8Array(12).map(() => Math.random() * 256); // INSECURE
|
|
85
|
+
|
|
86
|
+
// ❌ Don't reuse IV across messages
|
|
87
|
+
const fixedIv = new Uint8Array(12); // CATASTROPHIC for GCM
|
|
88
|
+
|
|
89
|
+
// ❌ Don't store keys in localStorage
|
|
90
|
+
localStorage.setItem('encryption_key', exportedKey); // Defeats the point
|
|
91
|
+
|
|
92
|
+
// ❌ Don't use SHA-1 for anything security-related
|
|
93
|
+
const hash = crypto.subtle.digest('SHA-1', data); // Use SHA-256 minimum
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Verification
|
|
97
|
+
|
|
98
|
+
After implementing encryption, verify with:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { aesgcm } from '@react-vault/core/encryption';
|
|
102
|
+
import { describe, expect, it } from 'vitest';
|
|
103
|
+
|
|
104
|
+
describe('encryption round-trip', () => {
|
|
105
|
+
it('produces different ciphertext each time (IV randomness)', async () => {
|
|
106
|
+
const key = await aesgcm.generateKey();
|
|
107
|
+
const a = await aesgcm.encrypt(key, 'test');
|
|
108
|
+
const b = await aesgcm.encrypt(key, 'test');
|
|
109
|
+
expect(a).not.toBe(b);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('decrypts to the original', async () => {
|
|
113
|
+
const key = await aesgcm.generateKey();
|
|
114
|
+
const ciphertext = await aesgcm.encrypt(key, 'test');
|
|
115
|
+
expect(await aesgcm.decrypt(key, ciphertext)).toBe('test');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('throws on tampered ciphertext', async () => {
|
|
119
|
+
const key = await aesgcm.generateKey();
|
|
120
|
+
const ciphertext = await aesgcm.encrypt(key, 'test');
|
|
121
|
+
const tampered = ciphertext.slice(0, -2) + 'XX';
|
|
122
|
+
await expect(aesgcm.decrypt(key, tampered)).rejects.toThrow();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
These three tests catch 95% of misuses.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bfsi-error-message
|
|
3
|
+
description: Reference for writing safe error messages — what users see, what gets logged, what gets sent to telemetry. Prevents stack traces, SQL, request IDs, or PII from leaking to UI or third-party services. Auto-loads when the user asks about error messages, error handling, exception messages, error boundaries, or Sentry/observability config.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Safe Error Messages
|
|
7
|
+
|
|
8
|
+
Reference for what's safe to show users, log to console, and send to telemetry.
|
|
9
|
+
|
|
10
|
+
## The three-tier model
|
|
11
|
+
|
|
12
|
+
Every error has three audiences:
|
|
13
|
+
|
|
14
|
+
| Tier | Audience | What they should see |
|
|
15
|
+
| ------------- | ---------------- | ---------------------------------------------------------- |
|
|
16
|
+
| **UI** | End user | Friendly, actionable, generic. NO technical detail. |
|
|
17
|
+
| **Logs** | Developers | Full technical detail, structured. PII scrubbed. |
|
|
18
|
+
| **Telemetry** | Sentry / Datadog | Anonymised stack + breadcrumbs. NO PII, NO request bodies. |
|
|
19
|
+
|
|
20
|
+
A single error gives different content to each tier.
|
|
21
|
+
|
|
22
|
+
## UI messages
|
|
23
|
+
|
|
24
|
+
### What users SHOULD see
|
|
25
|
+
|
|
26
|
+
- ✅ "Something went wrong. Please try again."
|
|
27
|
+
- ✅ "We couldn't process your transaction. Please check your details and try again. (Ref: ERR-A7K2)"
|
|
28
|
+
- ✅ "This PAN is already registered with another account."
|
|
29
|
+
- ✅ "Your session has expired. Please log in again."
|
|
30
|
+
|
|
31
|
+
### What users SHOULD NEVER see
|
|
32
|
+
|
|
33
|
+
- ❌ `TypeError: Cannot read properties of undefined (reading 'data')`
|
|
34
|
+
- ❌ `Failed to fetch: HTTP 500 from /api/v1/users/12345/kyc`
|
|
35
|
+
- ❌ `Connection refused: postgres://prod-db.internal:5432`
|
|
36
|
+
- ❌ `SQL syntax error near "DROP TABLE users"`
|
|
37
|
+
- ❌ Any raw error from the backend
|
|
38
|
+
- ❌ Stack traces
|
|
39
|
+
- ❌ Internal IDs (user IDs, session IDs — except a short ref code)
|
|
40
|
+
|
|
41
|
+
### Reference codes
|
|
42
|
+
|
|
43
|
+
Generate a short alphanumeric code (e.g. `ERR-A7K2`) that maps to the full error in logs. The user reads this to support; support looks it up. The user sees ONLY the code, never the underlying ID or message.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { generateErrorRef, recordError } from '@react-vault/core/audit';
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// ...
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const ref = generateErrorRef();
|
|
52
|
+
recordError(ref, err, { feature: 'kyc', action: 'submit' });
|
|
53
|
+
showToast({ title: t('errors.generic'), description: t('errors.ref', { ref }) });
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Log messages
|
|
58
|
+
|
|
59
|
+
Logs go to console (dev) and structured log shipper (prod). Full detail is fine, but:
|
|
60
|
+
|
|
61
|
+
- **PII scrubbed** — run through `auditClient.scrub()` before logging
|
|
62
|
+
- **Structured** — log as JSON (`pino`-style), not strings
|
|
63
|
+
- **Correlated** — include `request_id`, `user_id` (NOT email/name), `session_id`
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
log.error({
|
|
67
|
+
request_id: req.id,
|
|
68
|
+
user_id: user.id,
|
|
69
|
+
feature: 'kyc',
|
|
70
|
+
action: 'submit',
|
|
71
|
+
error: err.message,
|
|
72
|
+
stack: err.stack,
|
|
73
|
+
// NEVER: pan: user.pan, aadhaar: user.aadhaar
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Telemetry (Sentry)
|
|
78
|
+
|
|
79
|
+
Sentry is third-party and may be subject to different data residency rules. Treat its data as escaping your perimeter.
|
|
80
|
+
|
|
81
|
+
### Configure scrubbing
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
Sentry.init({
|
|
85
|
+
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
86
|
+
beforeSend(event) {
|
|
87
|
+
// Scrub PII from breadcrumbs, request bodies, URL params
|
|
88
|
+
return scrubSentryEvent(event);
|
|
89
|
+
},
|
|
90
|
+
ignoreErrors: [
|
|
91
|
+
// Browser extension noise
|
|
92
|
+
'top.GLOBALS',
|
|
93
|
+
'ResizeObserver loop limit exceeded',
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`scrubSentryEvent` (from `@react-vault/core/observability`) walks the event and:
|
|
99
|
+
|
|
100
|
+
- Removes `request.data` (request body)
|
|
101
|
+
- Removes URL query params matching PII patterns
|
|
102
|
+
- Replaces values in `extra` / `tags` / `user` that match PII patterns with `<scrubbed>`
|
|
103
|
+
- Trims breadcrumbs older than 30 seconds (to limit blast radius)
|
|
104
|
+
|
|
105
|
+
### Sentry user context
|
|
106
|
+
|
|
107
|
+
Set the bare minimum:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
Sentry.setUser({ id: user.id }); // ID only — NEVER email, name, mobile
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Error boundary
|
|
114
|
+
|
|
115
|
+
Wrap every route in `<BFSIErrorBoundary>` (from `@react-vault/ui`). It:
|
|
116
|
+
|
|
117
|
+
1. Catches the error
|
|
118
|
+
2. Generates a ref code
|
|
119
|
+
3. Records full detail to logs + telemetry (with scrubbing)
|
|
120
|
+
4. Shows a generic friendly message with the ref code
|
|
121
|
+
5. Provides a "go back" button
|
|
122
|
+
|
|
123
|
+
Never expose the actual error message via `componentDidCatch`'s `error.message` to JSX.
|
|
124
|
+
|
|
125
|
+
## When to throw vs when to swallow
|
|
126
|
+
|
|
127
|
+
- **Throw** when something genuinely went wrong and the calling code can't continue safely.
|
|
128
|
+
- **Swallow + log** when the operation can degrade gracefully (e.g. analytics failed — show UI anyway).
|
|
129
|
+
|
|
130
|
+
Never swallow silently. Even degraded paths should leave a log entry.
|
|
131
|
+
|
|
132
|
+
## Form validation errors (different rules)
|
|
133
|
+
|
|
134
|
+
Validation errors are NOT exceptions — they're expected user input. These can be specific:
|
|
135
|
+
|
|
136
|
+
- ✅ "PAN must be 10 characters: 5 letters, 4 digits, 1 letter"
|
|
137
|
+
- ✅ "Amount must be at least ₹100"
|
|
138
|
+
- ✅ "Mobile number must start with 6, 7, 8, or 9"
|
|
139
|
+
|
|
140
|
+
These come from Zod's `.message()` and are user-facing on purpose. Just keep them generic — don't include the _value_ the user typed back into the error.
|
|
141
|
+
|
|
142
|
+
## Edge cases
|
|
143
|
+
|
|
144
|
+
### Network errors
|
|
145
|
+
|
|
146
|
+
Don't show "Failed to fetch" or similar. Map to: "We couldn't reach our servers. Check your internet connection and try again."
|
|
147
|
+
|
|
148
|
+
### 401 (auth)
|
|
149
|
+
|
|
150
|
+
Don't show "Unauthorized" or "Token expired". Map to: "Your session has expired. Please log in again." Then redirect to login.
|
|
151
|
+
|
|
152
|
+
### 403 (permission)
|
|
153
|
+
|
|
154
|
+
Don't show "Forbidden" or "Insufficient permissions". Map to: "You don't have access to this. Contact your administrator if you think this is wrong."
|
|
155
|
+
|
|
156
|
+
### 5xx (server)
|
|
157
|
+
|
|
158
|
+
Don't show "Internal server error" or HTTP details. Map to: "We're having trouble right now. Please try again in a moment. (Ref: ERR-XXXX)"
|
|
159
|
+
|
|
160
|
+
### 429 (rate limit)
|
|
161
|
+
|
|
162
|
+
Map to: "Too many attempts. Please wait a moment and try again."
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bfsi-feature
|
|
3
|
+
description: Scaffolds a new BFSI feature module with the full directory structure (api, containers, components, routes, tests, i18n keys). Variant-aware — generates RTK Query OR TanStack Query code based on the project's stack. Use when the user types /bfsi-feature, asks to "scaffold a feature", "create a new feature module", "add a CRUD page", or "start a new BFSI module".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
argument-hint: <feature-name> [--variant rtk|tanstack] [--no-i18n]
|
|
6
|
+
allowed-tools: Read Write Edit Glob Grep Bash(mkdir:*) Bash(node:*)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# BFSI Feature Scaffold
|
|
10
|
+
|
|
11
|
+
Generates a complete feature module under `src/features/<FeatureName>/` following the Your Real Company BFSI architecture: container-component split, RTK Query / TanStack Query API layer, Zod validation, audit-wrapped mutations, accessible UI, i18n keys.
|
|
12
|
+
|
|
13
|
+
## Arguments
|
|
14
|
+
|
|
15
|
+
- `$0` — feature name in PascalCase (e.g. `KycVerification`, `LoanApplication`, `Transactions`). **Required.**
|
|
16
|
+
- `--variant rtk|tanstack` — overrides project default (auto-detected from `package.json`).
|
|
17
|
+
- `--no-i18n` — skip i18n key generation.
|
|
18
|
+
|
|
19
|
+
## What gets generated
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/features/<FeatureName>/
|
|
23
|
+
├── api.ts # RTK Query slice OR TanStack hook factories
|
|
24
|
+
├── schema.ts # Zod schemas (request, response, form)
|
|
25
|
+
├── types.ts # Inferred TS types from Zod
|
|
26
|
+
├── constants.ts # API URLs, cache tags, audit event names
|
|
27
|
+
├── routes.tsx # Feature routes with <ProtectedRoute>
|
|
28
|
+
├── containers/
|
|
29
|
+
│ ├── <FeatureName>List.tsx # Container: data + handlers
|
|
30
|
+
│ └── <FeatureName>Form.tsx # Container: form state
|
|
31
|
+
├── components/
|
|
32
|
+
│ ├── <FeatureName>Table.tsx # Presentational: receives props
|
|
33
|
+
│ ├── <FeatureName>FormFields.tsx # Presentational: form fields
|
|
34
|
+
│ └── <FeatureName>Actions.tsx # Audit-wrapped action buttons
|
|
35
|
+
├── hooks/
|
|
36
|
+
│ └── use<FeatureName>.ts # Custom hooks
|
|
37
|
+
├── utils/
|
|
38
|
+
│ └── mappers.ts # snake_case ↔ camelCase, value mappers
|
|
39
|
+
├── __tests__/
|
|
40
|
+
│ ├── containers.test.tsx
|
|
41
|
+
│ ├── schema.test.ts
|
|
42
|
+
│ └── e2e.spec.ts # Playwright
|
|
43
|
+
└── index.ts # Barrel export
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Plus updates to:
|
|
47
|
+
|
|
48
|
+
- `src/routes/index.tsx` — registers the new feature routes
|
|
49
|
+
- `src/i18n/translations/en.json` — adds `<feature>.*` namespace
|
|
50
|
+
- `src/i18n/translations/hi.json` — placeholder keys (translator fills in)
|
|
51
|
+
|
|
52
|
+
## Workflow
|
|
53
|
+
|
|
54
|
+
### Step 1: Validate inputs
|
|
55
|
+
|
|
56
|
+
Confirm:
|
|
57
|
+
|
|
58
|
+
- `$0` is PascalCase (regex `^[A-Z][A-Za-z0-9]+$`).
|
|
59
|
+
- The feature directory does NOT already exist.
|
|
60
|
+
- A `package.json` exists at the project root.
|
|
61
|
+
- Detect variant: look for `@reduxjs/toolkit` vs `@tanstack/react-query` in dependencies.
|
|
62
|
+
|
|
63
|
+
If validation fails, exit and tell the user what to fix.
|
|
64
|
+
|
|
65
|
+
### Step 2: Run the scaffold script
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/bfsi-feature/scripts/scaffold.mjs $0 --variant=<detected>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The script writes all files using the templates in `references/templates/`.
|
|
72
|
+
|
|
73
|
+
### Step 3: Verify
|
|
74
|
+
|
|
75
|
+
After generation:
|
|
76
|
+
|
|
77
|
+
1. Run `pnpm typecheck` and report any errors.
|
|
78
|
+
2. Run `pnpm lint` on the new files only.
|
|
79
|
+
3. Read the generated `routes.tsx` and confirm it's registered.
|
|
80
|
+
|
|
81
|
+
### Step 4: Summarise
|
|
82
|
+
|
|
83
|
+
Output a short summary to the user:
|
|
84
|
+
|
|
85
|
+
- N files created
|
|
86
|
+
- Routes registered: `/<feature>` and `/<feature>/:id`
|
|
87
|
+
- Next step suggestion: "Run `pnpm dev` and visit /<feature> to see the empty list. Then add fields to `schema.ts`."
|
|
88
|
+
|
|
89
|
+
## Conventions enforced
|
|
90
|
+
|
|
91
|
+
- **No `any` types.** All types flow from Zod schemas via `z.infer<>`.
|
|
92
|
+
- **No hardcoded strings.** All user-facing strings go through `t()` (or `<Trans>`).
|
|
93
|
+
- **Sensitive fields get `<PIIMaskedDisplay>` wrappers** by default if their names match `/^(pan|aadhaar|account|mobile|email|dob)$/i`.
|
|
94
|
+
- **All mutations go through `useAuditedMutation`** which logs the action.
|
|
95
|
+
- **All routes are `<ProtectedRoute>`** with an explicit `permission` prop.
|
|
96
|
+
- **All forms use `useFormWithZod`** (from `@react-vault/ui`) for consistent validation display.
|
|
97
|
+
|
|
98
|
+
## Examples
|
|
99
|
+
|
|
100
|
+
### Create a KYC feature
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/bfsi-feature KycVerification
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Result: `src/features/KycVerification/` populated. PAN, Aadhaar, address fields auto-wrapped with PIIMaskedDisplay. Routes `/kyc-verification` and `/kyc-verification/:id` registered with `permission="kyc.view"`.
|
|
107
|
+
|
|
108
|
+
### Override variant
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
/bfsi-feature LoanApplication --variant tanstack
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Force TanStack Query API layer even if project default is RTK.
|
|
115
|
+
|
|
116
|
+
## References
|
|
117
|
+
|
|
118
|
+
- Full file templates: [`references/templates/`](references/templates/)
|
|
119
|
+
- BFSI architecture rationale: [`references/architecture.md`](references/architecture.md)
|
|
120
|
+
- Audit event naming convention: [`references/audit-events.md`](references/audit-events.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# BFSI Feature — Architecture rationale
|
|
2
|
+
|
|
3
|
+
## Why container-component split?
|
|
4
|
+
|
|
5
|
+
**Containers** own all side-effects: API calls, audit logging, mutations, navigation, form state. They have no JSX of their own — just compose a presentational component and pass it props.
|
|
6
|
+
|
|
7
|
+
**Components** receive props, render UI, emit events. No `useFetch`, no `useDispatch`, no `useNavigate`. They are pure and easy to test with React Testing Library.
|
|
8
|
+
|
|
9
|
+
For BFSI, this split matters because:
|
|
10
|
+
|
|
11
|
+
- **Audit clarity** — security review only needs to look at containers to see what data flows in/out
|
|
12
|
+
- **Easy mocking** — components test in isolation; you don't fight network in unit tests
|
|
13
|
+
- **Reusability** — same component renders in two contexts with different containers (e.g. customer-view vs admin-view)
|
|
14
|
+
|
|
15
|
+
## Why Zod everywhere?
|
|
16
|
+
|
|
17
|
+
We use Zod for THREE distinct purposes:
|
|
18
|
+
|
|
19
|
+
1. **API response validation** — every API response is parsed through a Zod schema before reaching components. Rejects malformed responses (XSS defence).
|
|
20
|
+
2. **Form validation** — same schema or a derived schema validates form inputs.
|
|
21
|
+
3. **Type generation** — `z.infer<typeof schema>` gives us TS types from a single source of truth.
|
|
22
|
+
|
|
23
|
+
This means: edit one Zod schema, and types + runtime checks update together. No drift.
|
|
24
|
+
|
|
25
|
+
## Why audit-wrapped mutations?
|
|
26
|
+
|
|
27
|
+
Every state-changing API call must be audited for BFSI compliance (RBI Annexure I, SOC2 CC7.3). The `useAuditedMutation` hook from `@react-vault/core/audit` wraps RTK Query / TanStack Query mutations and:
|
|
28
|
+
|
|
29
|
+
1. Generates an event ID (UUID v4)
|
|
30
|
+
2. Records: actor, action, target, timestamp, request hash
|
|
31
|
+
3. POSTs to `/api/audit` with PII scrubbed
|
|
32
|
+
4. Records success/failure result
|
|
33
|
+
5. Returns the same shape as the underlying mutation
|
|
34
|
+
|
|
35
|
+
The container layer always uses `useAuditedMutation` instead of raw `useMutation`.
|
|
36
|
+
|
|
37
|
+
## Why permission-gated routes?
|
|
38
|
+
|
|
39
|
+
`<ProtectedRoute permission="kyc.view">` does TWO checks:
|
|
40
|
+
|
|
41
|
+
1. **Authentication** — is there a valid session?
|
|
42
|
+
2. **Authorization** — does the current user's permission set include `"kyc.view"`?
|
|
43
|
+
|
|
44
|
+
Failing #1 → redirect to login. Failing #2 → render 403 with audit log entry.
|
|
45
|
+
|
|
46
|
+
The permission string is checked against the current user's permission set fetched at login. We do NOT trust client-side permissions for security; the backend re-checks on every API call. The client check is a UX optimisation (don't show what they can't access) and an audit-log trigger.
|
|
47
|
+
|
|
48
|
+
## Sensitive field auto-wrap
|
|
49
|
+
|
|
50
|
+
The scaffolder auto-wraps fields whose names match `/^(pan|aadhaar|account|mobile|email|dob)$/i` with `<PIIMaskedDisplay>` in the table component. This means:
|
|
51
|
+
|
|
52
|
+
- The value is masked by default (`****1234` for account numbers, `ABCDE****F` for PAN, etc.)
|
|
53
|
+
- A reveal toggle fires an audit log event with reason
|
|
54
|
+
- Reveals expire after 30 seconds and re-mask
|
|
55
|
+
|
|
56
|
+
This is a default; you can opt out by editing the generated component. But by default, PII never appears unmasked in lists or tables.
|
|
57
|
+
|
|
58
|
+
## i18n namespace per feature
|
|
59
|
+
|
|
60
|
+
Each feature gets its own i18n namespace `<feature>.*`. This keeps translation files navigable (one team can own KYC translations, another Loans) and lets us lazy-load translation chunks per route.
|
|
61
|
+
|
|
62
|
+
Naming:
|
|
63
|
+
|
|
64
|
+
- `<feature>.title`
|
|
65
|
+
- `<feature>.list.empty`
|
|
66
|
+
- `<feature>.form.fields.<fieldName>.label`
|
|
67
|
+
- `<feature>.form.fields.<fieldName>.placeholder`
|
|
68
|
+
- `<feature>.errors.<errorCode>`
|
|
69
|
+
- `<feature>.actions.<actionName>`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Audit Event Naming Convention
|
|
2
|
+
|
|
3
|
+
Every state-changing action emits an audit event. Names follow:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
<feature>.<entity>.<action>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
|
|
11
|
+
- `kyc.verification.submitted`
|
|
12
|
+
- `kyc.verification.approved`
|
|
13
|
+
- `kyc.verification.rejected`
|
|
14
|
+
- `loan.application.created`
|
|
15
|
+
- `loan.application.documents_uploaded`
|
|
16
|
+
- `transaction.transfer.initiated`
|
|
17
|
+
- `transaction.transfer.confirmed`
|
|
18
|
+
- `user.profile.password_changed`
|
|
19
|
+
- `user.session.logged_in`
|
|
20
|
+
- `user.session.logged_out_idle`
|
|
21
|
+
- `data.account_number.revealed`
|
|
22
|
+
- `data.pan.revealed`
|
|
23
|
+
|
|
24
|
+
## Required event metadata
|
|
25
|
+
|
|
26
|
+
Every event must include:
|
|
27
|
+
|
|
28
|
+
| Field | Source | Notes |
|
|
29
|
+
| ------------------ | ----------------------------------- | ------------------------------------------------- |
|
|
30
|
+
| `event_id` | UUID v4 | Generated client-side |
|
|
31
|
+
| `event_name` | string | e.g. `kyc.verification.submitted` |
|
|
32
|
+
| `actor_id` | from session | User performing the action |
|
|
33
|
+
| `actor_session_id` | from session | Session correlation |
|
|
34
|
+
| `target_type` | string | e.g. `kyc_verification` |
|
|
35
|
+
| `target_id` | string | Resource ID |
|
|
36
|
+
| `timestamp` | ISO 8601 | Client clock — backend re-stamps for legal record |
|
|
37
|
+
| `outcome` | `success` \| `failure` \| `pending` | Always populated |
|
|
38
|
+
| `request_hash` | sha256 | Of request body, for tamper detection |
|
|
39
|
+
| `client_metadata` | object | User agent, viewport — no PII |
|
|
40
|
+
|
|
41
|
+
## Reveal events
|
|
42
|
+
|
|
43
|
+
When a user reveals a masked PII field, fire:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
data.<field>.revealed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With metadata `{ field, reason?, duration_ms }`. If `reason` is required by policy (e.g. for Aadhaar in some flows), the UI must prompt and include it.
|
|
50
|
+
|
|
51
|
+
## Failure events
|
|
52
|
+
|
|
53
|
+
For failed sensitive operations:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
<feature>.<entity>.<action>_failed
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With metadata including the error code (but NEVER the raw error message — that may contain PII).
|
|
60
|
+
|
|
61
|
+
## NEVER include in audit events
|
|
62
|
+
|
|
63
|
+
- Card numbers (raw or last-4)
|
|
64
|
+
- Passwords
|
|
65
|
+
- OTP codes
|
|
66
|
+
- Full PAN / Aadhaar (only masked, e.g. `****1234`)
|
|
67
|
+
- Full account numbers
|
|
68
|
+
- Free-text user input from forms (it might contain PII)
|
|
69
|
+
|
|
70
|
+
The `auditClient` in `@react-vault/core/audit` runs every payload through a PII scrubber before POST.
|