checkmyvibe 1.0.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 ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # checkmyvibe
2
+
3
+ A structured, zero-dependency, local security-audit workflow packaged as an Agent Skill for AI coding assistants (such as Claude Code, Cursor, Codex, and Gemini CLI).
4
+
5
+ AI-generated ("vibe-coded") applications built on modern AI tools frequently ship with serious, well-documented security flaws—such as exposed API keys, fake authentication stubs, permissive default database rules, and client-side pricing logic. **checkmyvibe** solves this by packaging security checks directly into an Agent Skill. When you ask your coding agent to "run a security audit," the agent uses checkmyvibe's local helper scripts and its own reasoning capabilities to analyze your codebase, producing a prioritized report detailing what's wrong, why it matters, and how to fix it.
6
+
7
+ ---
8
+
9
+ ## What This Checks For
10
+
11
+ * **Exposed Secrets & API Keys:** Identifies hardcoded API keys, JWT secrets, database credentials, and high-entropy strings across your files (using standard prefixes like `sk_live`, `AIza`, `AKIA`, and `ghp_`).
12
+ * **Version Control Leakage:** Verifies if sensitive files (like `.env`) exist in the project but are not properly excluded in your `.gitignore` file.
13
+ * **Fake & Stubbed Authentication:** Greps for common mock authentication markers (such as functions named `mockAuth`, `fakeLogin`, `tempAuth`, or logic that returns `true` unconditionally to bypass authentication checks).
14
+ * **Database Misconfigurations:** Identifies permissive default rules (such as `allow read, write: if true;` in Firebase/Firestore configs) and checks if Row-Level Security (RLS) is enabled on Supabase database schemas.
15
+ * **Broken Object-Level Authorization (BOLA/IDOR):** Analyzes API routes to check if endpoints fetch resources by ID without validating that the authenticated user owns or has permission to access that resource.
16
+ * **Client-Side Payment & Pricing Logic:** Scans checkout routes and payment integrations to check if prices or transaction values are calculated or accepted directly from client-side parameters rather than securely fetched on the server.
17
+
18
+ ---
19
+
20
+ ## What This Does NOT Check For (Disclaimer)
21
+
22
+ > [!WARNING]
23
+ > **checkmyvibe is not a substitute for a full professional security audit.**
24
+ > This tool is a first-pass heuristic scanner meant to highlight common mistakes made by AI coding models during rapid scaffolding. It does not perform dynamic runtime analysis, penetration testing, deep static analysis, dependency vulnerability checks, or comprehensive logic auditing. Do not rely solely on checkmyvibe to declare your application secure for production.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ### Method 1: Using npx (Recommended)
31
+ You can install and copy the skill automatically to your project or global environment by running:
32
+
33
+ ```bash
34
+ npx checkmyvibe
35
+ ```
36
+
37
+ The installer will detect if you have a `.claude` configuration directory in your project root and offer to install it either locally (project-level) or globally for your user.
38
+
39
+ ### Method 2: Manual Installation
40
+ If you prefer not to use `npx`, copy the files manually:
41
+
42
+ 1. Create a `checkmyvibe` directory inside your agent's skills path:
43
+ * **Project-level (Claude Code):** `.claude/skills/checkmyvibe/`
44
+ * **Global/Personal (Claude Code):** `~/.claude/skills/checkmyvibe/`
45
+ 2. Copy `SKILL.md`, the `scripts/` folder, and the `references/` folder into that directory.
46
+
47
+ ---
48
+
49
+ ## Usage
50
+
51
+ Once installed, your AI coding agent will automatically recognize the `checkmyvibe` skill when relevant. You can trigger the workflow explicitly:
52
+
53
+ ### Claude Code
54
+ Open your terminal in the workspace and type:
55
+ ```bash
56
+ /bug run a security audit
57
+ ```
58
+ *Or simply ask Claude in the chat:*
59
+ > "Run a security audit on this project using checkmyvibe"
60
+
61
+ The agent will walk through the checks, execute the local python validation scripts, and print a prioritized markdown report (Critical / Should Fix / Worth Reviewing) with instructions on how to correct the issues.
62
+
63
+ ---
64
+
65
+ ## License
66
+
67
+ This project is open-source and licensed under the [Apache 2.0 License](LICENSE).
package/SKILL.md ADDED
@@ -0,0 +1,326 @@
1
+ ---
2
+ name: checkmyvibe
3
+ description: >
4
+ Use this skill when the user asks for a security review, security audit, penetration test, or "is this safe to ship / safe to launch" check. Also trigger proactively when the agent is about to help deploy, publish, or push an application live, or when the codebase shows signs of AI-scaffolded patterns (Supabase/Firebase config, recently generated boilerplate, auth stubs, no existing security review) and the user has not had one done yet. Covers the specific, documented vulnerability patterns common in AI-generated and vibe-coded applications: exposed secrets, missing or fake authentication, permissive database rules, broken object-level authorization, unvalidated inputs, and client-side payment or pricing logic.
5
+ ---
6
+
7
+ # checkmyvibe — Security Audit for Vibe-Coded Apps
8
+
9
+ ## Why this skill exists
10
+
11
+ AI coding tools optimize for "it works," not "it's safe." A feature can pass every
12
+ manual test a non-technical founder runs — sign up, log in, place an order — and
13
+ still leak every other user's data to a stranger who changes one number in a URL.
14
+ This happens because AI-generated code frequently ships with scaffolding shortcuts
15
+ that were meant to be temporary (a stub auth check, a permissive default database
16
+ rule) and never get hardened before launch, precisely because the person building
17
+ the app doesn't know those shortcuts exist or what to look for.
18
+
19
+ Your job when this skill is active: think like a security engineer doing a
20
+ pre-launch review for a client who has never heard the words "IDOR" or "row-level
21
+ security." Find the real, exploitable issues. Explain them in plain language. Give
22
+ exact fixes. Do not pad the report with theoretical concerns that don't apply to
23
+ this specific codebase, and do not skip a check because the codebase "looks
24
+ simple" — simple codebases are exactly where stubbed auth and hardcoded secrets
25
+ hide, because nobody expected them to hold real user data yet.
26
+
27
+ ## Scope and honesty (read this before writing any report)
28
+
29
+ This is a first-pass audit for known, documented failure patterns. It is not a
30
+ full penetration test, it does not cover infrastructure security, dependency
31
+ vulnerabilities, or novel/business-logic-specific flaws outside the six categories
32
+ below. Never tell the user their app is "secure" or "safe" in an unqualified way.
33
+ The correct language is "no issues found in this pass" or "ready to ship as far as
34
+ these checks go" — always paired with the scope reminder in the Final Summary
35
+ section. If the app appears to handle payments, health data, or other regulated
36
+ data, say explicitly that a professional audit is strongly recommended regardless
37
+ of what this pass finds.
38
+
39
+ ## Before you start
40
+
41
+ 1. **Analyze and understand the whole project first:** Walk the directory tree and analyze the repository configuration files before running any checks, generating findings, or providing instructions. Establish a solid high-level understanding of the architecture, components, and data flow.
42
+ 2. Identify the stack: what backend/framework, what database or BaaS provider
43
+ (Supabase, Firebase, custom Postgres, etc.), what auth approach is in use.
44
+ This changes which checks apply and how to phrase findings.
45
+ 3. Identify what data the app handles: user accounts, payments, health data,
46
+ messages between users, files. This changes severity judgments — the same
47
+ missing check is more severe on an app handling payment data than on a toy
48
+ to-do list.
49
+ 4. Run the checks below in order. Use the bundled scripts where noted. For
50
+ everything else, read the actual code — don't rely on file names alone.
51
+
52
+ ---
53
+
54
+
55
+ ## Supported stacks to recognize
56
+
57
+ Common stacks this skill should expect include: Next.js, Express, FastAPI, Supabase, Firebase, Prisma, Postgres, MongoDB, Clerk, NextAuth, Stripe, and common app patterns built around them.
58
+
59
+ ## Evidence and confidence rules
60
+
61
+ - Only flag something when there is evidence in the code.
62
+ - Do not guess from filenames, comments, or variable names alone.
63
+ - If something looks suspicious but is not fully proven, mark it with a confidence tag such as `High confidence`, `Medium confidence`, or `Needs manual review`.
64
+ - Do not overflag. Prefer one precise finding over several weak or duplicate ones.
65
+
66
+ ## Fix priority
67
+
68
+ When multiple issues are found, sort them in this order:
69
+ Critical user exposure > auth bypass > IDOR > payment logic > config issues > hygiene.
70
+
71
+ ## What success looks like
72
+
73
+ The report should help the coding agent make the repo safer in the next commit, not just describe problems.
74
+
75
+ ## Check 1: Exposed secrets and credentials
76
+
77
+ **Run the script** `scripts/scan_secrets.py` (located relative to this `SKILL.md` file) against the project root. It flags known key
78
+ prefixes (`sk_live`, `sk_test`, `AIza`, `AKIA`, `ghp_`, `xox[bp]`), high-entropy
79
+ strings, and variable assignments where a name like `key`, `secret`, `token`, or
80
+ `password` is set to a literal string instead of `process.env.X` or equivalent.
81
+
82
+ **What counts as a finding:**
83
+ - A real credential committed directly in source, e.g.:
84
+ `const stripeKey = "sk_live_51H8x..."` — Critical
85
+ - A secret present only in `.env` but `.env` is not gitignored (see Check 2) —
86
+ Critical, because it WILL leak on the next commit even if it hasn't yet
87
+ - A secret exposed to the client bundle — e.g. a server-only key referenced in
88
+ frontend code, or a Next.js env var missing the required server-only scoping
89
+ (using a secret key where only `NEXT_PUBLIC_`-prefixed vars should appear) —
90
+ Critical, this is directly shippable to every visitor's browser
91
+
92
+ **What is NOT a finding (avoid false positives):**
93
+ - Public/anon/publishable keys that are designed to be exposed client-side
94
+ (Stripe publishable keys starting `pk_`, Supabase anon keys) — these are safe
95
+ by design as long as server-side authorization (RLS, API checks) is correctly
96
+ configured. Note this distinction explicitly in the report so the user isn't
97
+ confused about why one key is fine and another isn't.
98
+ - Example/placeholder values clearly meant as documentation, e.g. `"your-api-key-here"`
99
+
100
+ ## Check 2: .gitignore hygiene
101
+
102
+ **Run the script** `scripts/check_gitignore.py` (located relative to this `SKILL.md` file) against the project root. It checks whether `.env`, `.env.local`, and
103
+ similar files exist in the project and whether `.gitignore` actually excludes
104
+ them (not just whether a `.gitignore` file exists — many AI-generated `.gitignore`
105
+ files exist but miss the actual secret file).
106
+
107
+ **What counts as a finding:**
108
+ - `.env` present in the working directory and not listed in `.gitignore` —
109
+ Critical, regardless of current contents
110
+ - `.env` already committed to git history (check with a quick `git log --all
111
+ --full-history -- .env` if git is available) — Critical, and note in the
112
+ fix that removing it from `.gitignore` going forward is not enough; the
113
+ secrets in git history must be rotated, since they're recoverable even after
114
+ deletion
115
+
116
+ ## Check 3: Missing or fake authentication
117
+
118
+ **Run the script** `scripts/check_auth_patterns.py` (located relative to this `SKILL.md` file) against the project root as a first pass. Then search auth-related code (login handlers, middleware, route guards, session
119
+ checks) for these patterns:
120
+
121
+ **What counts as a finding:**
122
+ - A function that always returns true/success regardless of input, e.g.:
123
+ ```js
124
+ function isAuthenticated(req) {
125
+ return true; // TODO: implement real auth
126
+ }
127
+ ```
128
+ Critical — this is a placeholder someone forgot to replace.
129
+ - Naming that signals a stub: `mockAuth`, `fakeLogin`, `tempAuth`, `bypassAuth`,
130
+ `skipAuth`, `devAuth` still present in a codebase with no clear dev-only guard
131
+ around it. If it IS properly guarded (e.g. `if (process.env.NODE_ENV ===
132
+ 'development')`), verify the guard is airtight and note it as Should Fix
133
+ rather than Critical, since misconfigured environment variables in production
134
+ are a common way these leak through anyway.
135
+ - A protected route or API endpoint with no auth check at all — compare route
136
+ definitions against which ones return or modify user-specific data.
137
+ - Client-side-only auth checks — e.g. hiding a button in the UI if not logged in,
138
+ but the underlying API endpoint doesn't independently verify the session.
139
+ This is Critical: anyone can call the API directly, bypassing the UI entirely.
140
+ - Auth checks present but commented out, with a bypass left active nearby.
141
+
142
+ **Judgment note:** this check benefits most from your own reasoning rather than
143
+ pure pattern matching, since real projects name things inconsistently. If
144
+ something looks like it might be a stub but you're not certain, read the
145
+ function body fully before deciding — don't flag based on the name alone, and
146
+ don't clear something based on the name alone either.
147
+
148
+ ## Check 4: Database and backend-as-a-service misconfiguration
149
+
150
+ **Run the script** `scripts/check_db_config.py` (located relative to this `SKILL.md` file) against the project root as a first pass.
151
+
152
+ If the project uses **Supabase**: check for row-level security (RLS) policies on
153
+ every table that holds user-specific data. A table with RLS disabled, or enabled
154
+ with a policy like `USING (true)` for `SELECT`/`UPDATE`/`DELETE`, means any
155
+ authenticated (or even anonymous) user can read or modify every row.
156
+ ```sql
157
+ -- Finding example: this policy applies to ALL rows for ALL users
158
+ CREATE POLICY "allow all" ON orders FOR SELECT USING (true);
159
+
160
+ -- Correct pattern to recommend as the fix:
161
+ CREATE POLICY "users see own orders" ON orders
162
+ FOR SELECT USING (auth.uid() = user_id);
163
+ ```
164
+
165
+ If the project uses **Firebase**: check `firestore.rules` or `storage.rules` for
166
+ default-allow states, e.g. `allow read, write: if true;` on collections holding
167
+ user data, or rules left at the Firebase default test-mode state (which expires
168
+ but is often copy-pasted into production-like configs).
169
+
170
+ **Storage buckets**: check whether file storage (Supabase Storage, Firebase
171
+ Storage, S3) is set to public when it holds user-uploaded content that should be
172
+ private (profile documents, private images, receipts).
173
+
174
+ ## Check 5: Broken object-level authorization (IDOR)
175
+
176
+ This is the single most common serious flaw in vibe-coded apps and the one most
177
+ worth spending real time on. The pattern: an endpoint takes an ID from the
178
+ request and fetches/modifies a resource using that ID, without checking the
179
+ resource actually belongs to the requesting user.
180
+
181
+ ```js
182
+ // Finding example — Critical
183
+ app.get('/api/orders/:id', requireAuth, async (req, res) => {
184
+ const order = await db.query('SELECT * FROM orders WHERE id = $1', [req.params.id]);
185
+ res.json(order); // any logged-in user can read ANY order by guessing/incrementing the id
186
+ });
187
+
188
+ // Correct pattern to recommend as the fix
189
+ app.get('/api/orders/:id', requireAuth, async (req, res) => {
190
+ const order = await db.query(
191
+ 'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
192
+ [req.params.id, req.user.id]
193
+ );
194
+ if (!order) return res.status(404).json({ error: 'Not found' });
195
+ res.json(order);
196
+ });
197
+ ```
198
+
199
+ Check every route that takes an ID as a URL param, query string, or body field
200
+ and touches a database record. This applies to GET (data exposure), PUT/PATCH
201
+ (unauthorized modification), and DELETE (unauthorized deletion) — all three are
202
+ common and DELETE is often the most damaging.
203
+
204
+ ## Check 6: Unvalidated inputs
205
+
206
+ Spot-check forms and API endpoints for:
207
+ - No type or length validation on inputs before they're stored or used
208
+ - User input passed into a database query via string concatenation rather than
209
+ parameterized queries/an ORM (SQL injection risk)
210
+ - User input rendered into HTML without escaping (XSS risk), especially in
211
+ frameworks that don't auto-escape by default
212
+ - File uploads with no restriction on file type or size
213
+
214
+ This check doesn't need to be exhaustive — flag the clearest, highest-impact
215
+ examples rather than every single form field, and note in the summary that a
216
+ full input-validation review is a good idea if the codebase is large.
217
+
218
+ ## Check 7: Client-side payment or pricing logic
219
+
220
+ Search checkout/payment flows for the price, discount, or total being read from
221
+ data the client controls (a hidden form field, a request body value, a query
222
+ param) rather than looked up server-side from a trusted source.
223
+
224
+ ```js
225
+ // Finding example — Critical
226
+ app.post('/api/checkout', async (req, res) => {
227
+ const { productId, price } = req.body; // price is trusted from the client!
228
+ await chargeCard(req.user.paymentMethod, price);
229
+ });
230
+
231
+ // Correct pattern to recommend as the fix
232
+ app.post('/api/checkout', async (req, res) => {
233
+ const { productId } = req.body;
234
+ const product = await db.query('SELECT price FROM products WHERE id = $1', [productId]);
235
+ await chargeCard(req.user.paymentMethod, product.price); // price comes from the server
236
+ });
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Severity rubric
242
+
243
+ - **Critical** — exploitable right now by any user or visitor, exposes real user
244
+ data, or allows bypassing authentication or payment. Ship-blocking.
245
+ - **Should Fix** — a genuine weakness that requires more specific conditions to
246
+ exploit (e.g. requires knowing another user's exact ID, or only affects an
247
+ admin-only route with a smaller blast radius), or a control that exists but is
248
+ incomplete/inconsistent.
249
+ - **Worth Reviewing** — best-practice gap with low immediate exploitability
250
+ given the current app, but worth fixing before the app scales or handles more
251
+ sensitive data.
252
+
253
+ When in doubt between two levels, consider: could a stranger with no special
254
+ access do real harm to a real user right now? If yes, Critical.
255
+
256
+ ## Report format
257
+
258
+ For every finding, use exactly this structure:
259
+
260
+ - Include a short `Confidence:` line when the evidence is not fully conclusive.
261
+ - Include a `Generated fix:` line when a direct code snippet or precise implementation step would help the agent patch the issue immediately.
262
+
263
+ **[SEVERITY] Short title**
264
+ - **What's wrong:** plain-English description, no jargon. If a technical term is
265
+ unavoidable (e.g. "IDOR"), define it in one clause the first time it's used.
266
+ - **Why it matters:** a concrete, real-world consequence a non-technical person
267
+ would understand — not "this violates the principle of least privilege" but
268
+ "a stranger could see another user's home address and order history just by
269
+ changing a number in the browser's address bar."
270
+ - **The fix:** a specific code snippet or precise instruction, not a vague
271
+ suggestion like "add proper validation."
272
+ - **File(s):** exact path and line number(s).
273
+
274
+
275
+ ## After the checks
276
+
277
+ Once the checks are complete, create a security audit/report. **The report must be written in clear, plain English, completely free of dense security jargon, so that it is easily understandable by non-technical stakeholders (such as founders, clients, or project managers).** The report must include:
278
+ - what was found
279
+ - what is fixed already
280
+ - how each fix was applied
281
+ - what still needs attention
282
+ - the final readiness verdict
283
+
284
+ If no issues are found, still produce a short report that says no blocking issues were found in this pass, which categories were checked, and what the remaining scope limits are.
285
+
286
+
287
+ ## Example report style
288
+
289
+ Use clear, direct wording like:
290
+
291
+ **[Critical] Missing object-level authorization**
292
+ - **What's wrong:** Any logged-in user can read another user's order by changing the order ID.
293
+ - **Why it matters:** A stranger could see someone else's orders and personal details.
294
+ - **The fix:** Add a user ownership check in the query and return 404 when the record does not belong to the current user.
295
+ - **File(s):** `src/routes/orders.ts:42-58`
296
+
297
+ ## Final summary
298
+
299
+ End every audit with, in this order:
300
+ 1. Stack and data-sensitivity context noted at the start (one line)
301
+ 2. Total findings by severity, e.g. "2 Critical, 3 Should Fix, 1 Worth Reviewing"
302
+ 3. A one-line verdict: "Not ready to ship — fix the Critical items first" or "No
303
+ blocking issues found in this pass — review the Should Fix items when you can"
304
+ 4. The scope reminder: "This covers common patterns seen in AI-generated code —
305
+ exposed secrets, auth stubs, database misconfiguration, IDOR, input validation,
306
+ and client-side payment logic. It is not a full penetration test. If this app
307
+ handles payment, health, or other regulated data, get a professional security
308
+ review before launch regardless of these results."
309
+
310
+ ## Notes for the agent
311
+
312
+ - Always produce an audit/report after the scan; do not stop at raw findings.
313
+ - Write the entire report in clear, accessible language. Frame findings around concrete, real-world user consequences rather than abstract technical concepts. A non-technical stakeholder must be able to read the report and immediately understand the real-world danger.
314
+ - The audit should clearly separate what was found, what was fixed, how it was fixed, and what remains.
315
+ - Always run the available scripts (located in the `scripts/` directory relative to this `SKILL.md` file) before relying on reasoning alone for Checks 1-4 — they exist so those specific checks are reliable and repeatable rather than dependent on re-deriving the logic every time.
316
+ - If a script is missing, fails to run, or the language/stack isn't supported by
317
+ it, say so explicitly in the report ("automated secret scan could not run;
318
+ manually reviewed instead") rather than silently skipping the check.
319
+ - Do not invent findings to seem thorough. If a check finds nothing, report "No
320
+ issues found" for that category explicitly — silence looks like the check
321
+ wasn't performed at all.
322
+ - Do not flag the same underlying issue multiple times under different check
323
+ categories — pick the most relevant category and reference it once.
324
+ - If the codebase is large, prioritize routes and files that touch
325
+ authentication, payments, and any endpoint returning data tied to a specific
326
+ user ID — these are where real damage concentrates.
package/install.js ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+
8
+ // Source paths in the npm package
9
+ const srcSkill = path.join(__dirname, 'SKILL.md');
10
+ const srcScripts = path.join(__dirname, 'scripts');
11
+ const srcReferences = path.join(__dirname, 'references');
12
+
13
+ // Destination selection
14
+ const cwd = process.cwd();
15
+ const hasLocalClaude = fs.existsSync(path.join(cwd, '.claude'));
16
+
17
+ const localDest = path.join(cwd, '.claude', 'skills', 'checkmyvibe');
18
+ const globalDest = path.join(os.homedir(), '.claude', 'skills', 'checkmyvibe');
19
+
20
+ function copyRecursiveSync(src, dest) {
21
+ if (fs.statSync(src).isDirectory()) {
22
+ fs.mkdirSync(dest, { recursive: true });
23
+ fs.readdirSync(src).forEach((child) => {
24
+ copyRecursiveSync(path.join(src, child), path.join(dest, child));
25
+ });
26
+ } else {
27
+ fs.copyFileSync(src, dest);
28
+ }
29
+ }
30
+
31
+ function performInstall(destPath) {
32
+ try {
33
+ console.log(`\nInstalling checkmyvibe to: ${destPath}...`);
34
+
35
+ // Clear destination if it already exists (overwrite mode)
36
+ if (fs.existsSync(destPath)) {
37
+ if (fs.rmSync) {
38
+ fs.rmSync(destPath, { recursive: true, force: true });
39
+ } else {
40
+ fs.rmdirSync(destPath, { recursive: true });
41
+ }
42
+ }
43
+ fs.mkdirSync(destPath, { recursive: true });
44
+
45
+ // Copy SKILL.md
46
+ fs.copyFileSync(srcSkill, path.join(destPath, 'SKILL.md'));
47
+
48
+ // Copy folders
49
+ copyRecursiveSync(srcScripts, path.join(destPath, 'scripts'));
50
+ copyRecursiveSync(srcReferences, path.join(destPath, 'references'));
51
+
52
+ console.log('\n=========================================');
53
+ console.log('🎉 checkmyvibe Agent Skill installed!');
54
+ console.log('=========================================');
55
+ console.log('\nHow to run a security audit:');
56
+ console.log('1. Open your terminal in the target repository.');
57
+ console.log('2. Ask your coding agent: "run a security audit"');
58
+ console.log('3. The agent will execute checkmyvibe\'s checks and output a prioritized report!');
59
+ console.log('=========================================\n');
60
+ } catch (err) {
61
+ console.error('Error installing skill:', err.message);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ if (hasLocalClaude) {
67
+ // If .claude folder exists in the project root, install at project level directly
68
+ performInstall(localDest);
69
+ } else {
70
+ // Prompt user for local vs global/personal installation
71
+ const rl = readline.createInterface({
72
+ input: process.stdin,
73
+ output: process.stdout
74
+ });
75
+
76
+ console.log('No local .claude/ directory detected in the current working directory.');
77
+ console.log(`1. Install locally to current directory: ${path.join('.claude', 'skills', 'checkmyvibe')}`);
78
+ console.log(`2. Install globally/personally: ${globalDest.replace(os.homedir(), '~')}`);
79
+
80
+ rl.question('\nSelect installation destination (1 or 2, default 2): ', (answer) => {
81
+ rl.close();
82
+ const selection = answer.trim();
83
+ if (selection === '1') {
84
+ performInstall(localDest);
85
+ } else {
86
+ performInstall(globalDest);
87
+ }
88
+ });
89
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "checkmyvibe",
4
+ "version": "1.0.0",
5
+ "description": "A structured security-audit workflow Agent Skill for AI coding assistants to catch common vibe-coded vulnerabilities.",
6
+ "main": "install.js",
7
+ "bin": {
8
+ "checkmyvibe": "install.js"
9
+ },
10
+ "files": [
11
+ "SKILL.md",
12
+ "scripts/",
13
+ "references/",
14
+ "install.js",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "engines": {
19
+ "node": ">=14.0.0"
20
+ },
21
+ "keywords": [
22
+ "ai-agent",
23
+ "security-audit",
24
+ "claude-code",
25
+ "cursor",
26
+ "vibe-coding",
27
+ "agent-skill"
28
+ ],
29
+ "author": "Sarthak",
30
+ "license": "Apache-2.0"
31
+ }
@@ -0,0 +1,81 @@
1
+ # Vibe Vulnerability Patterns
2
+
3
+ This document serves as a reference for common vulnerability patterns identified in AI-generated ("vibe-coded") applications. AI coding assistants frequently introduce these security flaws because they prioritize speed, feature completion, and standalone component demonstration over robust security configurations.
4
+
5
+ ---
6
+
7
+ ## 1. Exposed Secrets
8
+
9
+ ### Overview
10
+ AI assistants often scaffold code with hardcoded API keys, JWT secrets, database connection strings, or third-party client keys directly inside configuration files or source code. Additionally, local environment files (`.env`) are frequently created without updating the project's `.gitignore` to prevent them from being committed to version control.
11
+
12
+ ### Why it happens in AI-generated code
13
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
14
+
15
+ ### The Correct Fix
16
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
17
+
18
+ ---
19
+
20
+ ## 2. Missing/Fake Authentication (Fake/Stubbed Auth)
21
+
22
+ ### Overview
23
+ When generating authentication flows, AI tools often implement mock authentication functions, fake login buttons, or bypass logic (e.g. returning `true` or dummy user objects unconditionally) to make testing quick. Developers may unknowingly deploy this placeholder logic directly to production, leaving endpoints unprotected.
24
+
25
+ ### Why it happens in AI-generated code
26
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
27
+
28
+ ### The Correct Fix
29
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
30
+
31
+ ---
32
+
33
+ ## 3. Database Misconfiguration (Permissive Database Rules / RLS)
34
+
35
+ ### Overview
36
+ Services like Supabase or Firebase Firestore rely on Row-Level Security (RLS) or security rules files to protect data access. AI assistants frequently scaffold projects with default allow-all rules (e.g. `allow read, write: if true;` or `CREATE POLICY ... TO public USING (true);`) or create tables in Supabase without enabling RLS, enabling any client with an anonymous key to query or modify all records.
37
+
38
+ ### Why it happens in AI-generated code
39
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
40
+
41
+ ### The Correct Fix
42
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
43
+
44
+ ---
45
+
46
+ ## 4. Broken Object-Level Authorization (BOLA / IDOR)
47
+
48
+ ### Overview
49
+ Broken Object-Level Authorization occurs when an application exposes a resource endpoint (e.g. `/api/orders/:id` or fetching data by a user-supplied ID parameter) and retrieves or updates the resource without verifying that the authenticated user owns or has permission to access that specific resource.
50
+
51
+ ### Why it happens in AI-generated code
52
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
53
+
54
+ ### The Correct Fix
55
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
56
+
57
+ ---
58
+
59
+ ## 5. Unvalidated Inputs
60
+
61
+ ### Overview
62
+ AI code often lacks backend input validation, trusting that client-side forms are sufficient. This opens the door to SQL injection, Cross-Site Scripting (XSS), server-side crashes due to unexpected data formats, or logic bypasses.
63
+
64
+ ### Why it happens in AI-generated code
65
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
66
+
67
+ ### The Correct Fix
68
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
69
+
70
+ ---
71
+
72
+ ## 6. Client-Side Payment Logic
73
+
74
+ ### Overview
75
+ AI tools often write payment flows where pricing calculations, transaction amounts, subscription statuses, or checkout processes are determined or validated entirely on the client side. Attackers can easily intercept and modify request bodies (e.g. setting a premium product price to `$0.00`) before sending it to a payment gateway API.
76
+
77
+ ### Why it happens in AI-generated code
78
+ [Placeholder: The user will fill in real-world examples and analysis of why this happens in AI-generated code.]
79
+
80
+ ### The Correct Fix
81
+ [Placeholder: The user will fill in the correct fix and mitigation strategies.]
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import re
5
+
6
+ SKIP_DIRS = {
7
+ 'node_modules', '.git', 'build', 'dist', '.next', '.svelte-kit',
8
+ '__pycache__', 'venv', '.venv', 'env', 'coverage', 'out'
9
+ }
10
+
11
+ SKIP_EXTS = {
12
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.pdf', '.zip', '.tar',
13
+ '.gz', '.mp3', '.mp4', '.mov', '.db', '.sqlite', '.exe', '.dll',
14
+ '.so', '.dylib', '.woff', '.woff2', '.ttf', '.eot', '.lock',
15
+ '.package-lock.json', '.pnpm-lock.yaml', '.yarn.lock', '.map',
16
+ '.svg', '.css', '.scss', '.less', '.html', '.xml'
17
+ }
18
+
19
+ # 1. Naming patterns for fake/mock/bypass authentication
20
+ AUTH_KEYWORDS = [
21
+ r'mockAuth', r'fakeLogin', r'tempAuth', r'bypassAuth',
22
+ r'mockLogin', r'fakeAuth', r'bypassLogin', r'dummyAuth',
23
+ r'bypassUser', r'mockUser', r'fakeUser'
24
+ ]
25
+ KEYWORD_REGEX = re.compile(r'(?i)\b(' + '|'.join(AUTH_KEYWORDS) + r')\b')
26
+
27
+ # 2. Stubbed functions or parameters returning true (always passing)
28
+ STUB_PATTERNS = [
29
+ # JS arrow function returning true: e.g. checkAuth = () => true
30
+ (re.compile(r'(?i)\b\w*(?:auth|login|signin|signup|admin|role|permission|credential)\w*\b\s*=\s*(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>\s*true\b(?!\s*==)'),
31
+ "Arrow function returning true directly"),
32
+
33
+ # JS standard function returning true: function checkAuth(...) { return true; }
34
+ (re.compile(r'(?i)\bfunction\s+\w*(?:auth|login|signin|signup|admin|role|permission|credential)\w*\b\s*\([^)]*\)\s*\{\s*return\s+true;?\s*\}'),
35
+ "Function returning true directly"),
36
+
37
+ # Python function returning True: def is_auth(...): return True
38
+ (re.compile(r'(?i)\bdef\s+\w*(?:auth|login|signin|signup|admin|role|permission|credential)\w*\b\s*\([^)]*\)\s*:\s*return\s+True\b'),
39
+ "Python function returning True directly"),
40
+
41
+ # Generic bypass function or property: e.g. "authBypass: true", "skipAuth: true"
42
+ (re.compile(r'(?i)\b(?:authBypass|skipAuth|disableAuth|bypassAuthentication|devAuth)\b\s*:\s*true\b'),
43
+ "Authentication bypass flag set to true")
44
+ ]
45
+
46
+ def is_binary(file_path):
47
+ try:
48
+ with open(file_path, 'rb') as f:
49
+ chunk = f.read(1024)
50
+ return b'\x00' in chunk
51
+ except Exception:
52
+ return True
53
+
54
+ def scan_file(file_path):
55
+ findings = []
56
+ if is_binary(file_path):
57
+ return findings
58
+
59
+ try:
60
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
61
+ content = f.read()
62
+
63
+ lines = content.splitlines()
64
+
65
+ # 1. Search for keyword matches line by line
66
+ for line_num, line in enumerate(lines, 1):
67
+ match = KEYWORD_REGEX.search(line)
68
+ if match:
69
+ findings.append((line_num, f"Mock/Bypass auth keyword '{match.group(1)}' found", line.strip()))
70
+
71
+ # 2. Search for stub patterns in the file content
72
+ for pattern, desc in STUB_PATTERNS:
73
+ for match in pattern.finditer(content):
74
+ start_pos = match.start()
75
+ # Find line number of this match
76
+ line_num = content.count('\n', 0, start_pos) + 1
77
+ matched_text = match.group(0).replace('\n', ' ').strip()
78
+ # Avoid adding multiple duplicate findings for the same line
79
+ if not any(f[0] == line_num for f in findings):
80
+ findings.append((line_num, f"Stubbed Auth: {desc}", matched_text))
81
+
82
+ except Exception:
83
+ pass
84
+
85
+ findings.sort(key=lambda x: x[0])
86
+ return findings
87
+
88
+ def main():
89
+ target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
90
+ target_dir = os.path.abspath(target_dir)
91
+
92
+ if not os.path.exists(target_dir):
93
+ print(f"Error: Path '{target_dir}' does not exist.")
94
+ sys.exit(1)
95
+
96
+ for root, dirs, files in os.walk(target_dir):
97
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
98
+
99
+ for file in files:
100
+ ext = os.path.splitext(file)[1].lower()
101
+ if ext in SKIP_EXTS or file.startswith('.'):
102
+ continue
103
+
104
+ file_path = os.path.join(root, file)
105
+ rel_path = os.path.relpath(file_path, target_dir)
106
+
107
+ findings = scan_file(file_path)
108
+ for line_num, desc, context in findings:
109
+ print(f"{rel_path}:{line_num}: {desc} -> `{context}`")
110
+
111
+ if __name__ == '__main__':
112
+ main()
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import re
5
+
6
+ SKIP_DIRS = {'node_modules', '.git', 'dist', 'build'}
7
+
8
+ # Permissive rules patterns for Firestore and Firebase Storage
9
+ FIREBASE_PERMISSIVE = re.compile(r'allow\s+[\w\s,]+:\s*if\s+true\s*;')
10
+
11
+ # Permissive Realtime Database rules
12
+ RTDB_PERMISSIVE = re.compile(r'"\.(read|write)"\s*:\s*"true"')
13
+
14
+ # Permissive policies in Supabase / SQL schema definition
15
+ SUPABASE_PERMISSIVE_POLICY = re.compile(r'(?i)create\s+policy\s+\w+\s+on\s+\w+\s+(?:for\s+\w+\s+)?to\s+(?:public|anon)\s+using\s*\(\s*true\s*\)')
16
+ SUPABASE_PERMISSIVE_POLICY_SIMPLE = re.compile(r'(?i)using\s*\(\s*true\s*\)|with\s+check\s*\(\s*true\s*\)')
17
+
18
+ def scan_firebase_rules(file_path):
19
+ """Scan Firestore/Storage rules for allow-all statements."""
20
+ findings = []
21
+ try:
22
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
23
+ for line_num, line in enumerate(f, 1):
24
+ clean_line = line.strip()
25
+ if FIREBASE_PERMISSIVE.search(clean_line):
26
+ findings.append((line_num, f"Permissive allow-all rule found: `{clean_line}`"))
27
+ except Exception:
28
+ pass
29
+ return findings
30
+
31
+ def scan_rtdb_rules(file_path):
32
+ """Scan Firebase Realtime Database rules for true reads/writes."""
33
+ findings = []
34
+ try:
35
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
36
+ for line_num, line in enumerate(f, 1):
37
+ clean_line = line.strip()
38
+ if RTDB_PERMISSIVE.search(clean_line):
39
+ findings.append((line_num, f"Permissive RTDB allow-all rule found: `{clean_line}`"))
40
+ except Exception:
41
+ pass
42
+ return findings
43
+
44
+ def scan_sql_rls(file_path):
45
+ """Scan SQL files to check if RLS is enabled on all tables and check for permissive policies."""
46
+ findings = []
47
+ try:
48
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
49
+ content = f.read()
50
+
51
+ # 1. Strip comments to avoid false matches (e.g. commented-out commands or code annotations)
52
+ # Strip single-line -- comments
53
+ content_no_comments = re.sub(r'--.*$', '', content, flags=re.M)
54
+ # Strip multiline /* */ comments
55
+ content_no_comments = re.sub(r'/\*.*?\*/', '', content_no_comments, flags=re.S)
56
+
57
+ # 2. Find all CREATE TABLE statements (support schemas, quotes, etc.)
58
+ created_tables = re.findall(r'(?i)create\s+table\s+(?:if\s+not\s+exists\s+)?([\w\.]+)', content_no_comments)
59
+ # Find all ENABLE ROW LEVEL SECURITY statements
60
+ enabled_rls = re.findall(r'(?i)alter\s+table\s+(?:if\s+exists\s+)?([\w\.]+)\s+enable\s+row\s+level\s+security', content_no_comments)
61
+
62
+ # Clean table names (strip quotes, normalize schemas)
63
+ def clean_table_name(t):
64
+ t = t.replace('"', '').replace("'", "")
65
+ if '.' in t:
66
+ t = t.split('.')[-1]
67
+ return t.lower()
68
+
69
+ created_clean = [clean_table_name(t) for t in created_tables]
70
+ enabled_clean = [clean_table_name(t) for t in enabled_rls]
71
+
72
+ for idx, table in enumerate(created_tables):
73
+ clean_t = clean_table_name(table)
74
+ if clean_t not in enabled_clean:
75
+ # Find line number in original file
76
+ escaped_t = re.escape(table)
77
+ match = re.search(r'(?i)create\s+table\s+(?:if\s+not\s+exists\s+)?' + escaped_t, content)
78
+ line_num = content.count('\n', 0, match.start()) + 1 if match else 1
79
+ findings.append((line_num, f"Table '{table}' is created but Row Level Security (RLS) is not enabled"))
80
+
81
+ # 3. Find permissive policies:
82
+ # Search for CREATE POLICY ... USING (true) or WITH CHECK (true)
83
+ # Handles multiline policy definitions and quoted policy names
84
+ policy_pattern = re.compile(
85
+ r'(?i)create\s+policy\s+("[^"]+"|[a-zA-Z_]\w*)\s+on\s+([\w\.]+)\s+[^;]+?(?:using|with\s+check)\s*\(\s*true\s*\)',
86
+ re.DOTALL
87
+ )
88
+
89
+ for match in policy_pattern.finditer(content_no_comments):
90
+ policy_name = match.group(1)
91
+ table_name = match.group(2)
92
+ # Find the character position of this policy match in the original content
93
+ orig_match = re.search(r'(?i)create\s+policy\s+' + re.escape(policy_name), content)
94
+ line_num = content.count('\n', 0, orig_match.start()) + 1 if orig_match else 1
95
+ findings.append((line_num, f"Permissive policy {policy_name} on table {table_name} allows public read/write via USING(true) / CHECK(true)"))
96
+
97
+ except Exception:
98
+ pass
99
+ return findings
100
+
101
+ def main():
102
+ target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
103
+ target_dir = os.path.abspath(target_dir)
104
+
105
+ findings_by_file = {}
106
+
107
+ for root, dirs, files in os.walk(target_dir):
108
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
109
+
110
+ for file in files:
111
+ file_path = os.path.join(root, file)
112
+ rel_path = os.path.relpath(file_path, target_dir)
113
+
114
+ # Check for Firebase Firestore/Storage rules
115
+ if file in ['firestore.rules', 'storage.rules']:
116
+ file_findings = scan_firebase_rules(file_path)
117
+ if file_findings:
118
+ findings_by_file[rel_path] = file_findings
119
+
120
+ # Check for Firebase Realtime Database rules
121
+ elif file == 'database.rules.json':
122
+ file_findings = scan_rtdb_rules(file_path)
123
+ if file_findings:
124
+ findings_by_file[rel_path] = file_findings
125
+
126
+ # Check for SQL migrations/schema files
127
+ elif file.endswith('.sql'):
128
+ file_findings = scan_sql_rls(file_path)
129
+ if file_findings:
130
+ findings_by_file[rel_path] = file_findings
131
+
132
+ # Output findings
133
+ if not findings_by_file:
134
+ print("PASS: No database security misconfigurations detected in configuration files.")
135
+ sys.exit(0)
136
+
137
+ print("FAIL: The following database security issues were found:")
138
+ for rel_path, file_findings in findings_by_file.items():
139
+ for line_num, desc in file_findings:
140
+ print(f"{rel_path}:{line_num}: {desc}")
141
+ sys.exit(1)
142
+
143
+ if __name__ == '__main__':
144
+ main()
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import fnmatch
5
+
6
+ # Potential local secret file patterns (glob format)
7
+ SECRET_PATTERNS = [
8
+ '.env',
9
+ '.env.*',
10
+ '*service-account*.json',
11
+ '*serviceAccount*.json',
12
+ '*credentials*.json',
13
+ '*.pem',
14
+ '*.key',
15
+ '*.p12',
16
+ 'secrets.json',
17
+ 'jwt_key*',
18
+ ]
19
+
20
+ # Patterns that are allowed to be public (not secrets)
21
+ PUBLIC_EXCEPTIONS = [
22
+ '*.example',
23
+ '*.template',
24
+ '*.sample',
25
+ '*.dist',
26
+ '*.config.json',
27
+ 'firebase.json',
28
+ ]
29
+
30
+ def is_secret_file(filename):
31
+ """Check if filename matches secret patterns and doesn't match public exceptions."""
32
+ is_secret = False
33
+ for pat in SECRET_PATTERNS:
34
+ if fnmatch.fnmatch(filename, pat):
35
+ is_secret = True
36
+ break
37
+
38
+ if not is_secret:
39
+ return False
40
+
41
+ for pat in PUBLIC_EXCEPTIONS:
42
+ if fnmatch.fnmatch(filename, pat):
43
+ return False
44
+
45
+ return True
46
+
47
+ def parse_gitignore(gitignore_path):
48
+ """Read and parse the .gitignore patterns, skipping comments and empty lines."""
49
+ patterns = []
50
+ if not os.path.exists(gitignore_path):
51
+ return patterns
52
+
53
+ with open(gitignore_path, 'r', encoding='utf-8', errors='ignore') as f:
54
+ for line in f:
55
+ line = line.strip()
56
+ if not line or line.startswith('#'):
57
+ continue
58
+ # Strip inline comments
59
+ if ' #' in line:
60
+ line = line.split(' #')[0].strip()
61
+ patterns.append(line)
62
+ return patterns
63
+
64
+ def is_ignored(file_rel_path, gitignore_patterns):
65
+ """Verify if a relative file path matches any .gitignore pattern."""
66
+ ignored = False
67
+ parts = file_rel_path.split(os.sep)
68
+
69
+ # Hardcoded check for system folders that are assumed ignored
70
+ if 'node_modules' in parts or '.git' in parts or 'dist' in parts or 'build' in parts:
71
+ return True
72
+
73
+ for pattern in gitignore_patterns:
74
+ negate = False
75
+ if pattern.startswith('!'):
76
+ negate = True
77
+ pattern = pattern[1:]
78
+
79
+ match = False
80
+ if pattern.startswith('/'):
81
+ clean_pattern = pattern[1:]
82
+ match = fnmatch.fnmatch(file_rel_path, clean_pattern) or fnmatch.fnmatch(file_rel_path, clean_pattern + '/*')
83
+ else:
84
+ match = fnmatch.fnmatch(os.path.basename(file_rel_path), pattern) or \
85
+ fnmatch.fnmatch(file_rel_path, pattern) or \
86
+ fnmatch.fnmatch(file_rel_path, '*/' + pattern) or \
87
+ any(fnmatch.fnmatch(part, pattern) for part in parts)
88
+
89
+ if match:
90
+ ignored = not negate
91
+
92
+ return ignored
93
+
94
+ def main():
95
+ target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
96
+ target_dir = os.path.abspath(target_dir)
97
+
98
+ gitignore_path = os.path.join(target_dir, '.gitignore')
99
+ has_gitignore = os.path.exists(gitignore_path)
100
+
101
+ gitignore_patterns = parse_gitignore(gitignore_path) if has_gitignore else []
102
+
103
+ secret_files_found = []
104
+ unignored_secrets = []
105
+
106
+ for root, dirs, files in os.walk(target_dir):
107
+ # Exclude directories
108
+ dirs[:] = [d for d in dirs if d not in {'node_modules', '.git', 'dist', 'build', '.next', '.svelte-kit'}]
109
+
110
+ for file in files:
111
+ if is_secret_file(file):
112
+ file_path = os.path.join(root, file)
113
+ rel_path = os.path.relpath(file_path, target_dir)
114
+
115
+ if is_ignored(rel_path, gitignore_patterns):
116
+ secret_files_found.append((rel_path, True))
117
+ else:
118
+ secret_files_found.append((rel_path, False))
119
+ unignored_secrets.append(rel_path)
120
+
121
+ if not secret_files_found:
122
+ print("PASS: No local secret files (like .env) were detected in the project.")
123
+ sys.exit(0)
124
+
125
+ if unignored_secrets:
126
+ print("FAIL: The following local secret files are NOT excluded by .gitignore:")
127
+ for sf in unignored_secrets:
128
+ print(f" - {sf}")
129
+ if not has_gitignore:
130
+ print(" (Note: No .gitignore file was found in the project root)")
131
+ sys.exit(1)
132
+ else:
133
+ print("PASS: All detected local secret files are correctly listed in .gitignore:")
134
+ for sf, ignored in secret_files_found:
135
+ print(f" - {sf} (ignored)")
136
+ sys.exit(0)
137
+
138
+ if __name__ == '__main__':
139
+ main()
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import re
5
+ import math
6
+
7
+ # Directories to skip during scanning
8
+ SKIP_DIRS = {
9
+ 'node_modules', '.git', 'build', 'dist', '.next', '.svelte-kit',
10
+ '__pycache__', 'venv', '.venv', 'env', 'coverage', 'out', '.expo',
11
+ '.output', '.nuxt'
12
+ }
13
+
14
+ # File extensions to skip (binary/media/lockfiles/assets)
15
+ SKIP_EXTS = {
16
+ '.png', '.jpg', '.jpeg', '.gif', '.ico', '.pdf', '.zip', '.tar',
17
+ '.gz', '.mp3', '.mp4', '.mov', '.db', '.sqlite', '.exe', '.dll',
18
+ '.so', '.dylib', '.woff', '.woff2', '.ttf', '.eot', '.lock',
19
+ '.package-lock.json', '.pnpm-lock.yaml', '.yarn.lock', '.map',
20
+ '.svg', '.css', '.scss', '.less', '.html', '.xml'
21
+ }
22
+
23
+ # Regexes for known API key formats
24
+ API_KEY_PATTERNS = [
25
+ (re.compile(r'AIza[0-9A-Za-z-_]{35}'), 'Google API Key'),
26
+ (re.compile(r'AKIA[0-9A-Z]{16}'), 'AWS Access Key ID'),
27
+ (re.compile(r'ghp_[a-zA-Z0-9]{36,40}'), 'GitHub Personal Access Token'),
28
+ (re.compile(r'sk_(live|test)_[0-9a-zA-Z]{24,96}'), 'Stripe Secret Key'),
29
+ (re.compile(r'xox[bapr]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}'), 'Slack Token'),
30
+ ]
31
+
32
+ # Regex for variable assignments with potential hardcoded secrets
33
+ # Matches: name = "string" or "name": "string", where name contains key, secret, token, password, credential, auth
34
+ ASSIGNMENT_PATTERN = re.compile(
35
+ r'(?i)(?:[\'"`]?)\b(\w*(?:key|secret|token|password|credential|auth)\w*)\b(?:[\'"`]?)\s*(?:=|:)\s*([\'"`])([^\'"`\n\r]{8,256})\2'
36
+ )
37
+
38
+ # Regex for standalone high-entropy candidate words (length 32 to 128)
39
+ HIGH_ENTROPY_WORD_PATTERN = re.compile(r'\b([a-zA-Z0-9+/=_-]{32,128})\b')
40
+
41
+ def calculate_entropy(s):
42
+ """Calculate the Shannon entropy of a string."""
43
+ if not s:
44
+ return 0
45
+ entropy = 0
46
+ for x in set(s):
47
+ p_x = s.count(x) / len(s)
48
+ entropy -= p_x * math.log2(p_x)
49
+ return entropy
50
+
51
+ def is_binary(file_path):
52
+ """Check if a file is binary by looking for null bytes."""
53
+ try:
54
+ with open(file_path, 'rb') as f:
55
+ chunk = f.read(1024)
56
+ return b'\x00' in chunk
57
+ except Exception:
58
+ return True
59
+
60
+ def scan_file(file_path):
61
+ findings = []
62
+ if is_binary(file_path):
63
+ return findings
64
+
65
+ try:
66
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
67
+ for line_num, line in enumerate(f, 1):
68
+ # 1. Check known API key patterns
69
+ for pattern, name in API_KEY_PATTERNS:
70
+ if pattern.search(line):
71
+ findings.append((line_num, f"Exposed {name}"))
72
+
73
+ # 2. Check variable assignments
74
+ for match in ASSIGNMENT_PATTERN.finditer(line):
75
+ var_name, quote, val = match.groups()
76
+
77
+ # Ignore environment variable references, string interpolation, or placeholders
78
+ if any(x in val for x in ['process.env', 'os.environ', 'os.getenv', 'ENV[', '${', '{{', '}}']):
79
+ continue
80
+
81
+ # Ignore common placeholder value patterns
82
+ val_lower = val.lower()
83
+ placeholders = {
84
+ 'true', 'false', 'null', 'undefined', 'placeholder', 'dummy',
85
+ 'your_key_here', 'your-key-here', 'your_secret_here', 'your-secret-here',
86
+ 'test', 'mock', 'none', 'value', 'secret', 'password', 'key', 'token'
87
+ }
88
+ if val_lower in placeholders or all(c in '-_*' for c in val):
89
+ continue
90
+
91
+ entropy = calculate_entropy(val)
92
+ if len(val) >= 12 and entropy > 3.0:
93
+ findings.append((line_num, f"Potential secret assigned to '{var_name}' (entropy: {entropy:.2f})"))
94
+ elif len(val) >= 16:
95
+ findings.append((line_num, f"Suspicious long literal assigned to '{var_name}'"))
96
+
97
+ # 3. Check for standalone high-entropy words (only if not already matched above)
98
+ # Keep threshold high to reduce noise from base64 assets/hashes
99
+ for match in HIGH_ENTROPY_WORD_PATTERN.finditer(line):
100
+ word = match.group(1)
101
+ # Skip if it is part of a URL, or has common non-secret structures
102
+ if any(x in line for x in ['http://', 'https://', 'src=', 'href=', 'url(']):
103
+ continue
104
+
105
+ # Skip common long words/hashes that are not secrets (e.g. standard CSS classes, hex colors)
106
+ if re.match(r'^[0-9a-fA-F]+$', word) and len(word) < 40:
107
+ # Small hex hashes are common (commit IDs, etc.) - require higher length or entropy
108
+ continue
109
+
110
+ entropy = calculate_entropy(word)
111
+ # True high entropy strings (like keys) typically have entropy > 4.2 for 32+ char length
112
+ if len(word) >= 32 and entropy > 4.3:
113
+ # Make sure it's not already reported in assignments
114
+ if not any(word in f[1] for f in findings):
115
+ findings.append((line_num, f"Standalone high-entropy string found (entropy: {entropy:.2f})"))
116
+
117
+ except Exception:
118
+ pass
119
+
120
+ return findings
121
+
122
+ def main():
123
+ target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
124
+ target_dir = os.path.abspath(target_dir)
125
+
126
+ if not os.path.exists(target_dir):
127
+ print(f"Error: Path '{target_dir}' does not exist.")
128
+ sys.exit(1)
129
+
130
+ for root, dirs, files in os.walk(target_dir):
131
+ # Exclude directories in-place
132
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
133
+
134
+ for file in files:
135
+ ext = os.path.splitext(file)[1].lower()
136
+ # Skip hidden files unless they are .env configuration files
137
+ if ext in SKIP_EXTS or (file.startswith('.') and not file.startswith('.env')):
138
+ continue
139
+
140
+ file_path = os.path.join(root, file)
141
+ rel_path = os.path.relpath(file_path, target_dir)
142
+
143
+ findings = scan_file(file_path)
144
+ for line_num, desc in findings:
145
+ print(f"{rel_path}:{line_num}: {desc}")
146
+
147
+ if __name__ == '__main__':
148
+ main()