100xprism 2.3.1
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 +21 -0
- package/README.md +196 -0
- package/VERSION +1 -0
- package/adapters/antigravity.sh +14 -0
- package/adapters/claude-code.sh +160 -0
- package/adapters/codex.sh +13 -0
- package/adapters/copilot.sh +13 -0
- package/adapters/cursor.sh +13 -0
- package/adapters/gemini.sh +13 -0
- package/adapters/lib/__pycache__/modules.cpython-312.pyc +0 -0
- package/adapters/lib/modules.py +592 -0
- package/adapters/lib/shared.sh +83 -0
- package/adapters/lib/sync_plugins.py +113 -0
- package/adapters/windsurf.sh +15 -0
- package/bin/100xprism.js +29 -0
- package/get.sh +24 -0
- package/install-project.sh +82 -0
- package/install.sh +281 -0
- package/lib/adapters/windows.js +429 -0
- package/lib/bootstrap.js +33 -0
- package/lib/init.js +19 -0
- package/lib/install.js +18 -0
- package/lib/migrate.js +52 -0
- package/lib/platform.js +22 -0
- package/lib/update.js +29 -0
- package/modules/_lib/reference.md +77 -0
- package/modules/a11y-auditor/SKILL.md +151 -0
- package/modules/ab-test-setup/SKILL.md +266 -0
- package/modules/ab-test-setup/evals/evals.json +105 -0
- package/modules/ab-test-setup/references/sample-size-guide.md +263 -0
- package/modules/ab-test-setup/references/test-templates.md +277 -0
- package/modules/ad-creative/SKILL.md +362 -0
- package/modules/ad-creative/evals/evals.json +90 -0
- package/modules/ad-creative/references/generative-tools.md +637 -0
- package/modules/ad-creative/references/platform-specs.md +213 -0
- package/modules/ai-seo/SKILL.md +398 -0
- package/modules/ai-seo/evals/evals.json +90 -0
- package/modules/ai-seo/references/content-patterns.md +285 -0
- package/modules/ai-seo/references/platform-ranking-factors.md +152 -0
- package/modules/analytics-tracking/SKILL.md +309 -0
- package/modules/analytics-tracking/evals/evals.json +90 -0
- package/modules/analytics-tracking/references/event-library.md +260 -0
- package/modules/analytics-tracking/references/ga4-implementation.md +300 -0
- package/modules/analytics-tracking/references/gtm-implementation.md +390 -0
- package/modules/architect/SKILL.md +282 -0
- package/modules/branch/SKILL.md +105 -0
- package/modules/churn-prevention/SKILL.md +424 -0
- package/modules/churn-prevention/evals/evals.json +93 -0
- package/modules/churn-prevention/references/cancel-flow-patterns.md +316 -0
- package/modules/churn-prevention/references/dunning-playbook.md +408 -0
- package/modules/cloud-security/SKILL.md +240 -0
- package/modules/cold-email/SKILL.md +178 -0
- package/modules/cold-email/evals/evals.json +94 -0
- package/modules/cold-email/references/benchmarks.md +83 -0
- package/modules/cold-email/references/follow-up-sequences.md +81 -0
- package/modules/cold-email/references/frameworks.md +90 -0
- package/modules/cold-email/references/personalization.md +79 -0
- package/modules/cold-email/references/subject-lines.md +53 -0
- package/modules/commit/SKILL.md +195 -0
- package/modules/competitor-alternatives/SKILL.md +256 -0
- package/modules/competitor-alternatives/evals/evals.json +93 -0
- package/modules/competitor-alternatives/references/content-architecture.md +271 -0
- package/modules/competitor-alternatives/references/templates.md +223 -0
- package/modules/connect/SKILL.md +894 -0
- package/modules/content-strategy/SKILL.md +359 -0
- package/modules/content-strategy/evals/evals.json +90 -0
- package/modules/context-dump/SKILL.md +67 -0
- package/modules/copy-editing/SKILL.md +447 -0
- package/modules/copy-editing/evals/evals.json +89 -0
- package/modules/copy-editing/references/plain-english-alternatives.md +394 -0
- package/modules/copywriting/SKILL.md +271 -0
- package/modules/copywriting/evals/evals.json +111 -0
- package/modules/copywriting/references/cold-email-benchmarks.md +83 -0
- package/modules/copywriting/references/cold-email-follow-ups.md +81 -0
- package/modules/copywriting/references/cold-email-frameworks.md +90 -0
- package/modules/copywriting/references/cold-email-personalization.md +79 -0
- package/modules/copywriting/references/cold-email-subject-lines.md +53 -0
- package/modules/copywriting/references/copy-frameworks.md +344 -0
- package/modules/copywriting/references/email-copy-guidelines.md +113 -0
- package/modules/copywriting/references/email-types.md +515 -0
- package/modules/copywriting/references/natural-transitions.md +272 -0
- package/modules/copywriting/references/sequence-templates.md +168 -0
- package/modules/data-query/SKILL.md +58 -0
- package/modules/data-viz/SKILL.md +225 -0
- package/modules/db/SKILL.md +205 -0
- package/modules/db/db-engines/_router.md +24 -0
- package/modules/db/db-engines/athena.md +16 -0
- package/modules/db/db-engines/cloud-sql.md +16 -0
- package/modules/db/db-engines/databricks.md +14 -0
- package/modules/db/db-engines/oracle.md +14 -0
- package/modules/db/db-engines/postgres.md +15 -0
- package/modules/db/db-engines/presto.md +14 -0
- package/modules/db/db-engines/snowflake.md +14 -0
- package/modules/docs/SKILL.md +100 -0
- package/modules/email-sequence/SKILL.md +309 -0
- package/modules/email-sequence/evals/evals.json +93 -0
- package/modules/email-sequence/references/copy-guidelines.md +113 -0
- package/modules/email-sequence/references/email-types.md +515 -0
- package/modules/email-sequence/references/sequence-templates.md +168 -0
- package/modules/enterprise-design/SKILL.md +75 -0
- package/modules/eval/SKILL.md +105 -0
- package/modules/figma-translator/SKILL.md +49 -0
- package/modules/fix-bugs/SKILL.md +104 -0
- package/modules/form-cro/SKILL.md +429 -0
- package/modules/form-cro/evals/evals.json +90 -0
- package/modules/free-tool-strategy/SKILL.md +178 -0
- package/modules/free-tool-strategy/evals/evals.json +90 -0
- package/modules/free-tool-strategy/references/tool-types.md +217 -0
- package/modules/gate/SKILL.md +232 -0
- package/modules/grill-me/SKILL.md +59 -0
- package/modules/interaction-engineer/SKILL.md +49 -0
- package/modules/issue/SKILL.md +272 -0
- package/modules/launch/SKILL.md +345 -0
- package/modules/launch-strategy/SKILL.md +353 -0
- package/modules/launch-strategy/evals/evals.json +91 -0
- package/modules/lint/SKILL.md +126 -0
- package/modules/marketing-ideas/SKILL.md +167 -0
- package/modules/marketing-ideas/evals/evals.json +90 -0
- package/modules/marketing-ideas/references/ideas-by-category.md +366 -0
- package/modules/marketing-psychology/SKILL.md +455 -0
- package/modules/marketing-psychology/evals/evals.json +88 -0
- package/modules/motion-designer/SKILL.md +214 -0
- package/modules/onboarding-cro/SKILL.md +220 -0
- package/modules/onboarding-cro/evals/evals.json +92 -0
- package/modules/onboarding-cro/references/experiments.md +258 -0
- package/modules/orchestrate/SKILL.md +77 -0
- package/modules/page-cro/SKILL.md +182 -0
- package/modules/page-cro/evals/evals.json +111 -0
- package/modules/page-cro/references/experiments.md +248 -0
- package/modules/page-cro/references/paywall-experiments.md +164 -0
- package/modules/paid-ads/SKILL.md +315 -0
- package/modules/paid-ads/evals/evals.json +90 -0
- package/modules/paid-ads/references/ad-copy-templates.md +207 -0
- package/modules/paid-ads/references/audience-targeting.md +243 -0
- package/modules/paid-ads/references/platform-setup-checklists.md +277 -0
- package/modules/paywall-upgrade-cro/SKILL.md +227 -0
- package/modules/paywall-upgrade-cro/evals/evals.json +93 -0
- package/modules/paywall-upgrade-cro/references/experiments.md +164 -0
- package/modules/popup-cro/SKILL.md +453 -0
- package/modules/popup-cro/evals/evals.json +94 -0
- package/modules/pr/SKILL.md +203 -0
- package/modules/pricing-strategy/SKILL.md +231 -0
- package/modules/pricing-strategy/evals/evals.json +90 -0
- package/modules/pricing-strategy/references/research-methods.md +152 -0
- package/modules/pricing-strategy/references/tier-structure.md +232 -0
- package/modules/product-marketing-context/SKILL.md +241 -0
- package/modules/product-marketing-context/evals/evals.json +85 -0
- package/modules/programmatic-seo/SKILL.md +238 -0
- package/modules/programmatic-seo/evals/evals.json +94 -0
- package/modules/programmatic-seo/references/playbooks.md +308 -0
- package/modules/push/SKILL.md +202 -0
- package/modules/referral-program/SKILL.md +255 -0
- package/modules/referral-program/evals/evals.json +89 -0
- package/modules/referral-program/references/affiliate-programs.md +164 -0
- package/modules/referral-program/references/program-examples.md +143 -0
- package/modules/release/SKILL.md +293 -0
- package/modules/revops/SKILL.md +343 -0
- package/modules/revops/evals/evals.json +91 -0
- package/modules/revops/references/automation-playbooks.md +290 -0
- package/modules/revops/references/lifecycle-definitions.md +278 -0
- package/modules/revops/references/routing-rules.md +203 -0
- package/modules/revops/references/scoring-models.md +247 -0
- package/modules/sales-enablement/SKILL.md +349 -0
- package/modules/sales-enablement/evals/evals.json +91 -0
- package/modules/sales-enablement/references/deck-frameworks.md +263 -0
- package/modules/sales-enablement/references/demo-scripts.md +355 -0
- package/modules/sales-enablement/references/objection-library.md +270 -0
- package/modules/sales-enablement/references/one-pager-templates.md +208 -0
- package/modules/schema-markup/SKILL.md +179 -0
- package/modules/schema-markup/evals/evals.json +87 -0
- package/modules/schema-markup/references/schema-examples.md +398 -0
- package/modules/security/SKILL.md +138 -0
- package/modules/seo-audit/SKILL.md +412 -0
- package/modules/seo-audit/evals/evals.json +136 -0
- package/modules/seo-audit/references/ai-writing-detection.md +200 -0
- package/modules/seo-audit/references/content-patterns.md +285 -0
- package/modules/seo-audit/references/platform-ranking-factors.md +152 -0
- package/modules/signup-flow-cro/SKILL.md +359 -0
- package/modules/signup-flow-cro/evals/evals.json +88 -0
- package/modules/site-architecture/SKILL.md +357 -0
- package/modules/site-architecture/evals/evals.json +88 -0
- package/modules/site-architecture/references/mermaid-templates.md +216 -0
- package/modules/site-architecture/references/navigation-patterns.md +305 -0
- package/modules/site-architecture/references/site-type-templates.md +293 -0
- package/modules/social-content/SKILL.md +278 -0
- package/modules/social-content/evals/evals.json +92 -0
- package/modules/social-content/references/platforms.md +170 -0
- package/modules/social-content/references/post-templates.md +177 -0
- package/modules/social-content/references/reverse-engineering.md +195 -0
- package/modules/spec/SKILL.md +81 -0
- package/modules/subagents/SKILL.md +123 -0
- package/modules/techdebt/SKILL.md +71 -0
- package/modules/terminal-setup/SKILL.md +49 -0
- package/modules/test/SKILL.md +493 -0
- package/modules/test/references/e2e-patterns.md +294 -0
- package/modules/update-claude-md/SKILL.md +52 -0
- package/modules/visual-system-architect/SKILL.md +53 -0
- package/package.json +44 -0
- package/plugins/plugins.json +43 -0
- package/shell/aliases.sh +24 -0
- package/shell/check-update.sh +212 -0
- package/templates/.env.example +199 -0
- package/templates/docker-compose.md +46 -0
- package/templates/node-frontend.md +56 -0
- package/templates/node-fullstack.md +59 -0
- package/templates/python-api.md +57 -0
- package/update.sh +231 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Dunning Playbook
|
|
2
|
+
|
|
3
|
+
Complete guide to recovering failed payments and reducing involuntary churn.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Why Dunning Matters
|
|
8
|
+
|
|
9
|
+
- Failed payments cause 30-50% of all subscription churn
|
|
10
|
+
- Most failed payments are recoverable with the right strategy
|
|
11
|
+
- Subscription businesses lose an estimated $129 billion annually to involuntary churn
|
|
12
|
+
- Effective dunning recovers 50-60% of failed payments
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The Dunning Timeline
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Day -30 to -7: Pre-dunning (prevent failures)
|
|
20
|
+
Day 0: Payment fails → Smart retry #1 + Email #1
|
|
21
|
+
Day 1-3: Smart retry #2 + Email #2
|
|
22
|
+
Day 3-5: Smart retry #3
|
|
23
|
+
Day 5-7: Smart retry #4 + Email #3
|
|
24
|
+
Day 7-10: Final retry + Email #4 (final warning)
|
|
25
|
+
Day 10-14: Grace period ends → Account paused/cancelled
|
|
26
|
+
Day 14+: Win-back sequence begins
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Pre-Dunning: Prevent Failures Before They Happen
|
|
32
|
+
|
|
33
|
+
### Card Expiry Management
|
|
34
|
+
|
|
35
|
+
| Timing | Action |
|
|
36
|
+
|--------|--------|
|
|
37
|
+
| 30 days before expiry | Email: "Your card ending in 4242 expires next month" |
|
|
38
|
+
| 15 days before expiry | Email: "Update your payment method to avoid interruption" |
|
|
39
|
+
| 7 days before expiry | Email: "Your card expires in 7 days — update now" |
|
|
40
|
+
| 3 days before expiry | In-app banner: "Payment method expiring soon" |
|
|
41
|
+
|
|
42
|
+
**Email template — Card expiring:**
|
|
43
|
+
```
|
|
44
|
+
Subject: Your card ending in 4242 expires soon
|
|
45
|
+
|
|
46
|
+
Hi [Name],
|
|
47
|
+
|
|
48
|
+
The card on file for your [Product] subscription expires on [date].
|
|
49
|
+
|
|
50
|
+
Update your payment method now to avoid any interruption:
|
|
51
|
+
|
|
52
|
+
[Update Payment Method →]
|
|
53
|
+
|
|
54
|
+
This takes less than 30 seconds.
|
|
55
|
+
|
|
56
|
+
— [Product] Team
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Card Updater Services
|
|
60
|
+
|
|
61
|
+
Major card networks offer automatic card update programs:
|
|
62
|
+
|
|
63
|
+
| Service | Network | What It Does |
|
|
64
|
+
|---------|---------|--------------|
|
|
65
|
+
| Visa Account Updater (VAU) | Visa | Auto-updates stored card numbers and expiry dates |
|
|
66
|
+
| Mastercard Automatic Billing Updater (ABU) | Mastercard | Same for Mastercard |
|
|
67
|
+
| Amex Cardrefresher | American Express | Same for Amex |
|
|
68
|
+
|
|
69
|
+
**Impact:** Reduces hard declines from expired/replaced cards by 30-50%.
|
|
70
|
+
|
|
71
|
+
**How to enable:**
|
|
72
|
+
- **Stripe**: Automatic — enabled by default
|
|
73
|
+
- **Chargebee**: Enabled through gateway settings
|
|
74
|
+
- **Recurly**: Built-in, enabled by default
|
|
75
|
+
- **Braintree**: Contact processor to enable
|
|
76
|
+
|
|
77
|
+
### Backup Payment Methods
|
|
78
|
+
|
|
79
|
+
Prompt for a second payment method:
|
|
80
|
+
- During signup: "Add a backup payment method" (low conversion)
|
|
81
|
+
- After first successful payment: "Protect your account with a backup card" (better timing)
|
|
82
|
+
- After a failed payment is recovered: "Add a backup to prevent future interruptions" (best timing — they felt the pain)
|
|
83
|
+
|
|
84
|
+
### Pre-Billing Notifications
|
|
85
|
+
|
|
86
|
+
For annual plans or high-value subscriptions:
|
|
87
|
+
- Email 7 days before renewal with amount and date
|
|
88
|
+
- Include link to update payment method
|
|
89
|
+
- Show what's included in the renewal
|
|
90
|
+
- Required by some regulations for auto-renewals
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Smart Retry Strategy
|
|
95
|
+
|
|
96
|
+
### Decline Type Classification
|
|
97
|
+
|
|
98
|
+
| Code | Type | Meaning | Retry? |
|
|
99
|
+
|------|------|---------|--------|
|
|
100
|
+
| `insufficient_funds` | Soft | Temporarily low balance | Yes — retry in 2-3 days |
|
|
101
|
+
| `card_declined` (generic) | Soft | Various temporary reasons | Yes — retry 3-4 times |
|
|
102
|
+
| `processing_error` | Soft | Gateway/network issue | Yes — retry within 24h |
|
|
103
|
+
| `expired_card` | Hard | Card is expired | No — request new card |
|
|
104
|
+
| `stolen_card` | Hard | Card reported stolen | No — request new card |
|
|
105
|
+
| `do_not_honor` | Soft/Hard | Bank refused (ambiguous) | Try once more, then ask for new card |
|
|
106
|
+
| `authentication_required` | Auth | SCA/3DS needed | Send customer to authenticate |
|
|
107
|
+
|
|
108
|
+
### Retry Schedule by Provider
|
|
109
|
+
|
|
110
|
+
**Stripe (Smart Retries — recommended):**
|
|
111
|
+
- Enable "Smart Retries" in Stripe Dashboard → Billing → Settings
|
|
112
|
+
- Stripe's ML model picks optimal retry timing based on billions of transactions
|
|
113
|
+
- Typically 4-8 retry attempts over 3-4 weeks
|
|
114
|
+
- Recovers ~15% more than fixed-schedule retries
|
|
115
|
+
|
|
116
|
+
**Manual retry schedule (if no smart retries):**
|
|
117
|
+
|
|
118
|
+
| Retry | Timing | Best Day/Time |
|
|
119
|
+
|-------|--------|--------------|
|
|
120
|
+
| 1 | Day 1 (24h after failure) | Morning, same day of week as original |
|
|
121
|
+
| 2 | Day 3 | Try a different time of day |
|
|
122
|
+
| 3 | Day 5 | After typical payday (1st, 15th) |
|
|
123
|
+
| 4 | Day 7 | Morning of the next business day |
|
|
124
|
+
| 5 (final) | Day 10 | Last attempt before grace period ends |
|
|
125
|
+
|
|
126
|
+
**Retry timing insights:**
|
|
127
|
+
- Retry on the same day of month the original payment succeeded
|
|
128
|
+
- Retry after common paydays (1st and 15th of the month)
|
|
129
|
+
- Avoid retrying on weekends (lower approval rates)
|
|
130
|
+
- Morning retries (8-10am local time) perform slightly better
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Dunning Email Sequence
|
|
135
|
+
|
|
136
|
+
### Email 1: Payment Failed (Day 0)
|
|
137
|
+
|
|
138
|
+
**Tone:** Friendly, matter-of-fact. No alarm.
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Subject: Action needed — your payment didn't go through
|
|
142
|
+
|
|
143
|
+
Hi [Name],
|
|
144
|
+
|
|
145
|
+
We tried to charge your [card type] ending in [last 4] for your
|
|
146
|
+
[Product] subscription ($[amount]), but it didn't go through.
|
|
147
|
+
|
|
148
|
+
This happens sometimes — usually a quick card update fixes it.
|
|
149
|
+
|
|
150
|
+
[Update Payment Method →]
|
|
151
|
+
|
|
152
|
+
Your access isn't affected yet. We'll retry automatically, but
|
|
153
|
+
updating your card is the fastest fix.
|
|
154
|
+
|
|
155
|
+
Need help? Just reply to this email.
|
|
156
|
+
|
|
157
|
+
— [Product] Team
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Email 2: Reminder (Day 3)
|
|
161
|
+
|
|
162
|
+
**Tone:** Helpful, slightly more urgent.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Subject: Quick reminder — update your payment for [Product]
|
|
166
|
+
|
|
167
|
+
Hi [Name],
|
|
168
|
+
|
|
169
|
+
Just a heads-up — we still haven't been able to process your
|
|
170
|
+
$[amount] payment for [Product].
|
|
171
|
+
|
|
172
|
+
[Update Payment Method →]
|
|
173
|
+
|
|
174
|
+
Takes less than 30 seconds. Your [data/projects/team access]
|
|
175
|
+
is safe, but we'll need a valid payment method to keep your
|
|
176
|
+
account active.
|
|
177
|
+
|
|
178
|
+
Questions? Reply here and we'll help.
|
|
179
|
+
|
|
180
|
+
— [Product] Team
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Email 3: Urgency (Day 7)
|
|
184
|
+
|
|
185
|
+
**Tone:** Direct, clear consequences.
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Subject: Your [Product] account will be paused in 3 days
|
|
189
|
+
|
|
190
|
+
Hi [Name],
|
|
191
|
+
|
|
192
|
+
We've tried to process your payment several times, but your
|
|
193
|
+
[card type] ending in [last 4] keeps getting declined.
|
|
194
|
+
|
|
195
|
+
If we don't receive payment by [date], your account will be
|
|
196
|
+
paused and you'll lose access to:
|
|
197
|
+
|
|
198
|
+
• [Key feature/data they use]
|
|
199
|
+
• [Their projects/workspace]
|
|
200
|
+
• [Team access for X members]
|
|
201
|
+
|
|
202
|
+
[Update Payment Method Now →]
|
|
203
|
+
|
|
204
|
+
Your data won't be deleted — you can reactivate anytime by
|
|
205
|
+
updating your payment method.
|
|
206
|
+
|
|
207
|
+
— [Product] Team
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Email 4: Final Warning (Day 10)
|
|
211
|
+
|
|
212
|
+
**Tone:** Final, clear, no guilt.
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
Subject: Last chance to keep your [Product] account active
|
|
216
|
+
|
|
217
|
+
Hi [Name],
|
|
218
|
+
|
|
219
|
+
This is our last reminder. Your payment of $[amount] is past
|
|
220
|
+
due, and your account will be paused tomorrow ([date]).
|
|
221
|
+
|
|
222
|
+
[Update Payment Method →]
|
|
223
|
+
|
|
224
|
+
After pausing:
|
|
225
|
+
• Your data is saved for [90 days]
|
|
226
|
+
• You can reactivate anytime
|
|
227
|
+
• Just update your card to restore access
|
|
228
|
+
|
|
229
|
+
If you intended to cancel, no action needed — your account
|
|
230
|
+
will be paused automatically.
|
|
231
|
+
|
|
232
|
+
— [Product] Team
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Grace Period Management
|
|
238
|
+
|
|
239
|
+
### What Happens During Grace Period
|
|
240
|
+
|
|
241
|
+
| Setting | Recommendation |
|
|
242
|
+
|---------|---------------|
|
|
243
|
+
| Duration | 7-14 days after final retry |
|
|
244
|
+
| Access | Degraded (read-only) or full access |
|
|
245
|
+
| Visibility | In-app banner: "Payment past due — update to continue" |
|
|
246
|
+
| Retry | Continue background retries during grace |
|
|
247
|
+
| Communication | Dunning emails continue |
|
|
248
|
+
|
|
249
|
+
### Access Degradation Options
|
|
250
|
+
|
|
251
|
+
**Option A: Full access during grace (recommended for B2B)**
|
|
252
|
+
- Lower friction, customer feels respected
|
|
253
|
+
- Higher recovery rate (they still see value)
|
|
254
|
+
- Risk: some customers exploit the grace period
|
|
255
|
+
|
|
256
|
+
**Option B: Read-only access (recommended for B2C)**
|
|
257
|
+
- Can view but not create/edit
|
|
258
|
+
- Creates urgency without data loss fear
|
|
259
|
+
- Clear message: "Update payment to resume full access"
|
|
260
|
+
|
|
261
|
+
**Option C: Immediate lockout (not recommended)**
|
|
262
|
+
- Aggressive, damages relationship
|
|
263
|
+
- Lower recovery rate
|
|
264
|
+
- Only appropriate for very low-cost plans
|
|
265
|
+
|
|
266
|
+
### Post-Grace Period
|
|
267
|
+
|
|
268
|
+
| Timing | Action |
|
|
269
|
+
|--------|--------|
|
|
270
|
+
| Grace period ends | Pause account (not delete) |
|
|
271
|
+
| Day 1 post-pause | "Your account has been paused" email |
|
|
272
|
+
| Day 7 post-pause | "Your data is still here" reminder |
|
|
273
|
+
| Day 30 post-pause | Win-back attempt with new offer |
|
|
274
|
+
| Day 60 post-pause | Final win-back |
|
|
275
|
+
| Day 90 post-pause | Data deletion warning (if applicable) |
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Provider-Specific Setup
|
|
280
|
+
|
|
281
|
+
### Stripe
|
|
282
|
+
|
|
283
|
+
**Enable Smart Retries:**
|
|
284
|
+
1. Dashboard → Settings → Billing → Subscriptions and emails
|
|
285
|
+
2. Enable "Smart Retries" under retry rules
|
|
286
|
+
3. Set failed payment emails in Dashboard → Settings → Emails
|
|
287
|
+
|
|
288
|
+
**Custom retry rules (if not using Smart Retries):**
|
|
289
|
+
```
|
|
290
|
+
Retry 1: 3 days after failure
|
|
291
|
+
Retry 2: 5 days after failure
|
|
292
|
+
Retry 3: 7 days after failure
|
|
293
|
+
Final: Mark subscription as unpaid after last retry
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Webhook events to handle:**
|
|
297
|
+
- `invoice.payment_failed` — trigger dunning
|
|
298
|
+
- `invoice.paid` — cancel dunning, restore access
|
|
299
|
+
- `customer.subscription.updated` — status changes
|
|
300
|
+
- `customer.subscription.deleted` — final cancellation
|
|
301
|
+
|
|
302
|
+
### Chargebee
|
|
303
|
+
|
|
304
|
+
**Built-in dunning:**
|
|
305
|
+
1. Settings → Configure Chargebee → Retry Settings
|
|
306
|
+
2. Configure retry attempts and intervals
|
|
307
|
+
3. Settings → Configure Chargebee → Email Notifications → Dunning
|
|
308
|
+
|
|
309
|
+
**Dunning options:**
|
|
310
|
+
- Automatic retries with configurable schedule
|
|
311
|
+
- Built-in dunning emails (customizable templates)
|
|
312
|
+
- Grace period configuration per plan
|
|
313
|
+
|
|
314
|
+
### Paddle
|
|
315
|
+
|
|
316
|
+
**Managed dunning:**
|
|
317
|
+
- Paddle handles retries and dunning automatically
|
|
318
|
+
- Limited customization (Paddle manages the relationship)
|
|
319
|
+
- Webhook: `subscription.payment_failed`, `subscription.cancelled`
|
|
320
|
+
- Best for hands-off approach
|
|
321
|
+
|
|
322
|
+
### Recurly
|
|
323
|
+
|
|
324
|
+
**Revenue Recovery:**
|
|
325
|
+
1. Configuration → Dunning Management
|
|
326
|
+
2. Set retry schedule per plan
|
|
327
|
+
3. Configure grace period and final action (pause vs cancel)
|
|
328
|
+
|
|
329
|
+
**Advanced features:**
|
|
330
|
+
- Machine-learning retry optimization
|
|
331
|
+
- Per-plan dunning schedules
|
|
332
|
+
- Built-in Account Updater
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## In-App Dunning
|
|
337
|
+
|
|
338
|
+
Don't rely on email alone. Show payment failures in the app:
|
|
339
|
+
|
|
340
|
+
### Banner Pattern
|
|
341
|
+
```
|
|
342
|
+
┌──────────────────────────────────────────────────────┐
|
|
343
|
+
│ ⚠ Your payment of $29 failed. Update your card to │
|
|
344
|
+
│ avoid losing access. [Update Payment →] [Dismiss] │
|
|
345
|
+
└──────────────────────────────────────────────────────┘
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Rules:**
|
|
349
|
+
- Show on every page load during dunning period
|
|
350
|
+
- Allow dismiss (but show again next session)
|
|
351
|
+
- Direct link to payment update (fewest clicks possible)
|
|
352
|
+
- Don't block the product — let them continue using it
|
|
353
|
+
|
|
354
|
+
### Modal Pattern (for final warning)
|
|
355
|
+
```
|
|
356
|
+
┌─────────────────────────────────────┐
|
|
357
|
+
│ │
|
|
358
|
+
│ Your account will be paused │
|
|
359
|
+
│ on [date] │
|
|
360
|
+
│ │
|
|
361
|
+
│ Update your payment method to │
|
|
362
|
+
│ keep access to your [X] projects │
|
|
363
|
+
│ and [Y] team members. │
|
|
364
|
+
│ │
|
|
365
|
+
│ [Update Payment Method] │
|
|
366
|
+
│ [Remind Me Later] │
|
|
367
|
+
│ │
|
|
368
|
+
└─────────────────────────────────────┘
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Measuring Dunning Performance
|
|
374
|
+
|
|
375
|
+
### Key Metrics
|
|
376
|
+
|
|
377
|
+
| Metric | How to Calculate | Target |
|
|
378
|
+
|--------|-----------------|--------|
|
|
379
|
+
| Recovery rate | Recovered payments / Total failed | 50-60% |
|
|
380
|
+
| Recovery rate by decline type | Recovered / Failed per type | Soft: 70%+, Hard: 40%+ |
|
|
381
|
+
| Time to recovery | Days from failure to successful payment | <5 days |
|
|
382
|
+
| Pre-dunning prevention rate | Prevented failures / Expected failures | 20-30% |
|
|
383
|
+
| Dunning email open rate | Opens / Sent per email | 60%+ |
|
|
384
|
+
| Dunning email click rate | Clicks / Opens per email | 30%+ |
|
|
385
|
+
| Revenue recovered (monthly) | Sum of recovered payment amounts | Track trend |
|
|
386
|
+
| Revenue lost to involuntary churn | Sum of failed + unrecovered amounts | Track trend |
|
|
387
|
+
|
|
388
|
+
### Benchmarking
|
|
389
|
+
|
|
390
|
+
**By company stage:**
|
|
391
|
+
|
|
392
|
+
| Stage | Typical Involuntary Churn | Target After Optimization |
|
|
393
|
+
|-------|--------------------------|--------------------------|
|
|
394
|
+
| Early (< $1M ARR) | 3-5% of MRR/month | 1-2% |
|
|
395
|
+
| Growth ($1-10M ARR) | 2-4% of MRR/month | 0.5-1.5% |
|
|
396
|
+
| Scale ($10M+ ARR) | 1-3% of MRR/month | 0.3-0.8% |
|
|
397
|
+
|
|
398
|
+
### ROI Calculation
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
Monthly failed payment MRR: $10,000
|
|
402
|
+
Current recovery rate: 30% ($3,000 recovered)
|
|
403
|
+
Target recovery rate: 60% ($6,000 recovered)
|
|
404
|
+
Monthly improvement: $3,000/month
|
|
405
|
+
Annual improvement: $36,000/year
|
|
406
|
+
Cost of dunning optimization: ~$200-500/month (tooling)
|
|
407
|
+
ROI: 6-15x
|
|
408
|
+
```
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloud-security
|
|
3
|
+
description: Rigorous security and data privacy scan for cloud deployments. Covers GCP infrastructure hardening, data privacy (PII/GDPR/CCPA), API security, container security, and compliance.
|
|
4
|
+
category: quality
|
|
5
|
+
tier: on-demand
|
|
6
|
+
slash_command: /cloud-security
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Cloud Security — Cloud Security & Data Privacy Scan
|
|
10
|
+
|
|
11
|
+
Rigorous security and data privacy scan for cloud deployments. Covers GCP infrastructure hardening, data privacy (PII/GDPR/CCPA), API security, container security, and compliance.
|
|
12
|
+
|
|
13
|
+
## Do NOT ask for permission — scan everything, fix what you can, report the rest.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 0 — Detect cloud config
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
PROJECT_ROOT=$(git rev-parse --show-toplevel)
|
|
21
|
+
INSTRUCTION_FILE=$(for f in CLAUDE.md AGENTS.md .cursorrules .windsurfrules; do [ -f "$PROJECT_ROOT/$f" ] && echo "$PROJECT_ROOT/$f" && break; done)
|
|
22
|
+
GCP_PROJECTS=$(grep -rh "GCP_PROJECT\|GOOGLE_CLOUD_PROJECT\|gcloud.*--project" ${INSTRUCTION_FILE:-/dev/null} .env.example terraform/ 2>/dev/null | grep -oE '[a-z][a-z0-9-]{4,28}' | sort -u | grep -v "^--" || true)
|
|
23
|
+
HAS_DOCKER=$(ls Dockerfile docker-compose.yml 2>/dev/null | head -1 || true)
|
|
24
|
+
HAS_TERRAFORM=$(ls terraform/*.tf infra/*.tf 2>/dev/null | head -1 || true)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Section 1 — GCP IAM & Access Control
|
|
30
|
+
|
|
31
|
+
For each GCP project detected:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
35
|
+
echo "=== IAM: $PROJECT ==="
|
|
36
|
+
gcloud projects get-iam-policy "$PROJECT" --format=json 2>/dev/null \
|
|
37
|
+
| python3 -c "
|
|
38
|
+
import sys,json
|
|
39
|
+
p=json.load(sys.stdin)
|
|
40
|
+
bad=['roles/editor','roles/owner','roles/iam.securityAdmin','roles/storage.admin']
|
|
41
|
+
[print('[HIGH]',b['role'],m) for b in p.get('bindings',[]) for m in b['members'] if b['role'] in bad and ('serviceAccount' in m or 'allUsers' in m)]"
|
|
42
|
+
gcloud iam service-accounts list --project="$PROJECT" --format="value(email)" 2>/dev/null \
|
|
43
|
+
| while read SA; do
|
|
44
|
+
n=$(gcloud iam service-accounts keys list --iam-account="$SA" --filter="keyType=USER_MANAGED" --format="value(KEY_ID)" 2>/dev/null | wc -l)
|
|
45
|
+
[ "$n" -gt 0 ] && echo "[HIGH] $SA: $n user-managed key(s) — use Workload Identity instead"
|
|
46
|
+
done
|
|
47
|
+
gcloud projects get-iam-policy "$PROJECT" --format=json 2>/dev/null | grep -E "allUsers|allAuthenticatedUsers" && echo "[CRITICAL] Public IAM binding found" || true
|
|
48
|
+
done
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Section 2 — GCP Network & Firewall
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
57
|
+
echo "=== Network: $PROJECT ==="
|
|
58
|
+
gcloud compute firewall-rules list --project="$PROJECT" --format=json 2>/dev/null \
|
|
59
|
+
| python3 -c "
|
|
60
|
+
import sys,json
|
|
61
|
+
sp={'22','3389','5432','6379','3306','27017','6443'}
|
|
62
|
+
[print('[CRITICAL] Firewall',r['name'],'port',p,'open to 0.0.0.0/0') for r in json.load(sys.stdin) if '0.0.0.0/0' in r.get('sourceRanges',[]) or '::/0' in r.get('sourceRanges',[]) for a in r.get('allowed',[]) for p in a.get('ports',[]) if str(p) in sp]"
|
|
63
|
+
gcloud sql instances list --project="$PROJECT" --format=json 2>/dev/null \
|
|
64
|
+
| python3 -c "
|
|
65
|
+
import sys,json
|
|
66
|
+
for i in json.load(sys.stdin):
|
|
67
|
+
n,ip=i['name'],i.get('settings',{}).get('ipConfiguration',{})
|
|
68
|
+
[print('[CRITICAL] Cloud SQL',n,'public IP open to',net['value']) if net.get('value') in ('0.0.0.0/0','::/0') else print('[MEDIUM] Cloud SQL',n,'public IP enabled') for net in ip.get('authorizedNetworks',[])] if ip.get('ipv4Enabled') else None
|
|
69
|
+
print('[HIGH] Cloud SQL',n,'SSL not required') if not ip.get('requireSsl') and ip.get('sslMode','')!='ENCRYPTED_ONLY' else print('[PASS] Cloud SQL',n,'SSL enforced')
|
|
70
|
+
print('[HIGH] Cloud SQL',n,'backups disabled') if not i.get('settings',{}).get('backupConfiguration',{}).get('enabled') else None"
|
|
71
|
+
done
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Section 3 — GCP Data Storage (GCS Buckets)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
80
|
+
echo "=== Storage: $PROJECT ==="
|
|
81
|
+
gcloud storage buckets list --project="$PROJECT" --format="value(name)" 2>/dev/null | while read BUCKET; do
|
|
82
|
+
gcloud storage buckets get-iam-policy "gs://$BUCKET" --format=json 2>/dev/null \
|
|
83
|
+
| grep -qE "allUsers|allAuthenticatedUsers" && echo "[CRITICAL] Bucket gs://$BUCKET is public" || true
|
|
84
|
+
[ "$(gcloud storage buckets describe "gs://$BUCKET" --format="value(iamConfiguration.uniformBucketLevelAccess.enabled)" 2>/dev/null)" != "True" ] \
|
|
85
|
+
&& echo "[MEDIUM] Bucket gs://$BUCKET: uniform access not enabled" || true
|
|
86
|
+
[ "$(gcloud storage buckets describe "gs://$BUCKET" --format="value(versioning.enabled)" 2>/dev/null)" != "True" ] \
|
|
87
|
+
&& echo "[LOW] Bucket gs://$BUCKET: versioning not enabled" || true
|
|
88
|
+
done
|
|
89
|
+
done
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Section 4 — GCP Cloud Run Security
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
98
|
+
echo "=== Cloud Run: $PROJECT ==="
|
|
99
|
+
gcloud run services list --project="$PROJECT" --format=json --region=us-central1 2>/dev/null \
|
|
100
|
+
| python3 -c "
|
|
101
|
+
import sys,json
|
|
102
|
+
[print('[HIGH] Cloud Run',s['metadata']['name'],'env',e['name'],'may contain hardcoded secret') for s in json.load(sys.stdin) for c in s.get('spec',{}).get('template',{}).get('spec',{}).get('containers',[]) for e in c.get('env',[]) if len(e.get('value',''))>20 and not e.get('value','').startswith('\$(') and 'secretKeyRef' not in str(e)]" 2>/dev/null || true
|
|
103
|
+
gcloud run services list --project="$PROJECT" --region=us-central1 --format="value(SERVICE)" 2>/dev/null \
|
|
104
|
+
| while read SVC; do
|
|
105
|
+
gcloud run services get-iam-policy "$SVC" --project="$PROJECT" --region=us-central1 --format=json 2>/dev/null \
|
|
106
|
+
| grep -q "allUsers" && echo "[INFO] Cloud Run $SVC: public — verify app-level auth" || true
|
|
107
|
+
done
|
|
108
|
+
done
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Section 5 — GCP Audit Logging
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
117
|
+
echo "=== Audit Logging: $PROJECT ==="
|
|
118
|
+
gcloud projects get-iam-policy "$PROJECT" --format=json 2>/dev/null \
|
|
119
|
+
| python3 -c "
|
|
120
|
+
import sys,json
|
|
121
|
+
p=json.load(sys.stdin)
|
|
122
|
+
svcs={c['service'] for c in p.get('auditConfigs',[]) for l in c.get('auditLogConfigs',[]) if l.get('logType') in ('DATA_READ','DATA_WRITE')}
|
|
123
|
+
[print('[PASS] Audit logging:',s) if s in svcs else print('[MEDIUM] Audit logging not enabled:',s) for s in ['cloudsql.googleapis.com','storage.googleapis.com','secretmanager.googleapis.com']]"
|
|
124
|
+
done
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Section 6 — Secret Manager Hygiene
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
for PROJECT in $GCP_PROJECTS; do
|
|
133
|
+
echo "=== Secrets: $PROJECT ==="
|
|
134
|
+
gcloud secrets list --project="$PROJECT" --format=json 2>/dev/null \
|
|
135
|
+
| python3 -c "
|
|
136
|
+
import sys,json
|
|
137
|
+
[print('[LOW] Secret',s['name'].split('/')[-1],'no rotation policy') for s in json.load(sys.stdin) if not s.get('rotation')]"
|
|
138
|
+
done
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Section 7 — Container Security (if Dockerfile present)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
if [ -n "$HAS_DOCKER" ]; then
|
|
147
|
+
echo "=== Container Security ==="
|
|
148
|
+
grep -rq "^USER root\|USER 0" Dockerfile 2>/dev/null && echo "[HIGH] Dockerfile: runs as root" || \
|
|
149
|
+
{ grep -q "^USER" Dockerfile 2>/dev/null && echo "[PASS] non-root USER set" || echo "[HIGH] Dockerfile: no USER directive (defaults to root)"; }
|
|
150
|
+
grep -qE "^(ARG|ENV)\s+\w+(KEY|SECRET|PASSWORD|TOKEN|PASS)=\S+" Dockerfile 2>/dev/null \
|
|
151
|
+
&& echo "[CRITICAL] Dockerfile: hardcoded secret in ARG/ENV" || echo "[PASS] No hardcoded secrets"
|
|
152
|
+
grep "^FROM" Dockerfile 2>/dev/null | head -1 | grep -q ":latest" \
|
|
153
|
+
&& echo "[MEDIUM] Dockerfile: base image uses :latest — pin version" || echo "[PASS] Base image pinned"
|
|
154
|
+
grep -q "^RUN chown" Dockerfile 2>/dev/null && echo "[LOW] Dockerfile: use COPY --chown instead of RUN chown" || true
|
|
155
|
+
fi
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Section 8 — Data Privacy: PII in Source Code
|
|
161
|
+
|
|
162
|
+
Scan for PII patterns that should never appear in source code or logs.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
echo "=== PII in Source Code ==="
|
|
166
|
+
EXCL="--exclude-dir=node_modules --exclude-dir=venv --exclude-dir=dist"
|
|
167
|
+
# Hardcoded emails (non-test/example)
|
|
168
|
+
grep -rn $EXCL '[a-zA-Z0-9._%+-]\+@[a-zA-Z0-9.-]\+\.[a-zA-Z]\{2,\}' --include="*.ts" --include="*.tsx" --include="*.py" --include="*.js" . 2>/dev/null \
|
|
169
|
+
| grep -v "example\.com\|test\|mock\|\.spec\.\|\.test\.\|noreply@\|support@\|hello@\|admin@" | head -5 \
|
|
170
|
+
| grep -q . && echo "[HIGH] Real email addresses found in source" || true
|
|
171
|
+
# SSN / credit card patterns
|
|
172
|
+
grep -rn $EXCL '[0-9]\{3\}-[0-9]\{2\}-[0-9]\{4\}' --include="*.ts" --include="*.py" . 2>/dev/null | head -5 | grep -q . && echo "[CRITICAL] SSN pattern in source" || true
|
|
173
|
+
grep -rn $EXCL '[0-9]\{4\}[- ][0-9]\{4\}[- ][0-9]\{4\}[- ][0-9]\{4\}' --include="*.ts" --include="*.py" . 2>/dev/null | head -5 | grep -q . && echo "[CRITICAL] Credit card pattern in source" || true
|
|
174
|
+
# PII in logs / error responses
|
|
175
|
+
grep -rn $EXCL "console\.log\|logger\." --include="*.ts" --include="*.py" --exclude="*.test.*" . 2>/dev/null \
|
|
176
|
+
| grep -iE "email|password|ssn|credit.card|phone|dob|social.security" | head -5 \
|
|
177
|
+
| grep -q . && echo "[HIGH] PII field names in log statements" || echo "[PASS] No PII in log statements"
|
|
178
|
+
grep -rn $EXCL "res\.json\|res\.send\|return.*error" --include="*.ts" --exclude="*.test.*" . 2>/dev/null \
|
|
179
|
+
| grep -iE "email|password|user\." | grep -v "error\.message\|//\|generic" | head -5 \
|
|
180
|
+
| grep -q . && echo "[MEDIUM] Possible PII in API error responses — verify generic" || true
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Section 9 — Data Privacy Compliance Checklist
|
|
186
|
+
|
|
187
|
+
Review manually. Report gaps.
|
|
188
|
+
|
|
189
|
+
GDPR/CCPA: □ Privacy Policy □ Data Inventory □ Consent □ Right to Delete □ Right to Export □ Data Retention □ Third-party DPAs □ Breach Response □ Data Minimization □ Encryption at Rest □ Encryption Transit □ Audit Trail
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
grep -rn "DELETE.*user\|deleteUser\|deactivate" api/ src/ 2>/dev/null | head -3 || echo "[GAP] No delete user endpoint found"
|
|
193
|
+
grep -rn "export.*user\|userExport\|data.*export" api/ src/ 2>/dev/null | head -3 || echo "[GAP] No export user endpoint found"
|
|
194
|
+
grep -rn "retention\|expires\|cleanup\|purge" api/ 2>/dev/null | head -3 || echo "[GAP] No retention policy found"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Section 10 — API Security Hygiene
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
echo "=== API Security ==="
|
|
203
|
+
X="--exclude-dir=node_modules"
|
|
204
|
+
grep -rn $X "helmet\|Content-Security-Policy\|Strict-Transport" --include="*.ts" --include="*.py" . 2>/dev/null | grep -q . \
|
|
205
|
+
&& echo "[PASS] Security headers configured" || echo "[HIGH] No security headers (helmet/CSP) found"
|
|
206
|
+
grep -rn $X "rateLimit\|rate.limit\|RateLimiter\|throttle" --include="*.ts" --include="*.py" . 2>/dev/null | grep -q . \
|
|
207
|
+
&& echo "[PASS] Rate limiting configured" || echo "[MEDIUM] No rate limiting found"
|
|
208
|
+
grep -rn $X "origin.*['\"]\\*['\"]" --include="*.ts" --include="*.py" . 2>/dev/null | grep -q . \
|
|
209
|
+
&& echo "[HIGH] CORS wildcard (*) origin found" || echo "[PASS] No CORS wildcard"
|
|
210
|
+
grep -rn $X --exclude="*.test.*" "query.*\`.*\${" --include="*.ts" --include="*.py" . 2>/dev/null | grep -v '\$\${' | grep -q . \
|
|
211
|
+
&& echo "[CRITICAL] SQL injection risk — string interpolation in queries" || echo "[PASS] No SQL injection patterns"
|
|
212
|
+
grep -rn $X --exclude="*.test.*" "eval[(]" --include="*.ts" --include="*.tsx" --include="*.js" . 2>/dev/null | grep -q . \
|
|
213
|
+
&& echo "[HIGH] unsafe-eval usage found" || echo "[PASS] No unsafe-eval usage"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Section 11 — Terraform / IaC Security (if present)
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
if [ -n "$HAS_TERRAFORM" ]; then
|
|
222
|
+
echo "=== Terraform Security ==="
|
|
223
|
+
grep -rn "roles/editor\|roles/owner\|storage\.admin\|secretmanager\.admin" terraform/ infra/ 2>/dev/null \
|
|
224
|
+
| grep -v "^#\|\.terraform" | grep -q . && echo "[HIGH] Overly broad IAM role in Terraform" || echo "[PASS] No overly broad IAM roles"
|
|
225
|
+
grep -rn "CHANGE_ME\|password.*default\|default.*password" terraform/ infra/ 2>/dev/null \
|
|
226
|
+
| grep -v "^#" | grep -q . && echo "[CRITICAL] Default/placeholder password in Terraform" || true
|
|
227
|
+
grep -rn "0\.0\.0\.0/0\|all_traffic\|allUsers" terraform/ infra/ 2>/dev/null \
|
|
228
|
+
| grep -v "^#\|egress" | grep -q . && echo "[HIGH] Possible public resource in Terraform" || true
|
|
229
|
+
fi
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Final report
|
|
235
|
+
|
|
236
|
+
Summarize results per section: S1 IAM | S2 Network | S3 Storage | S4 Cloud Run | S5 Audit | S6 Secrets | S7 Container | S8 PII | S9 Privacy | S10 API | S11 Terraform
|
|
237
|
+
|
|
238
|
+
Report totals: CRITICAL: N HIGH: N MEDIUM: N LOW: N
|
|
239
|
+
|
|
240
|
+
**Gate 5:** BLOCKED if any CRITICAL or HIGH. PASSED if zero critical + zero high.
|