@jikida/init 0.2.1 → 0.4.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/bin/init.js +95 -10
- package/package.json +1 -1
- package/skill/SKILL.md +7 -0
- package/skill/checklists/pre-deploy.md +46 -0
- package/skill/checklists/supabase-firebase.md +38 -0
package/bin/init.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Everything is idempotent: re-running skips already-done steps.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { readFileSync, existsSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
15
|
+
import { readFileSync, existsSync, writeFileSync, appendFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
18
18
|
import { execSync, spawnSync } from 'node:child_process';
|
|
@@ -117,13 +117,42 @@ function installSkill() {
|
|
|
117
117
|
// Master skill (full body) + one thin bootstrap per assistant, so "install into
|
|
118
118
|
// every editor you already use" actually holds — Cursor, Windsurf, Cline,
|
|
119
119
|
// Copilot, Aider, Continue, Claude, Gemini, plus the generic AGENTS.md.
|
|
120
|
+
// The Continue.dev config is YAML with a `rules:` block, not free prose — wrap
|
|
121
|
+
// the bootstrap so it drops in as a valid rule instead of breaking the parser.
|
|
122
|
+
const continueYaml =
|
|
123
|
+
'name: Jikida security\n' +
|
|
124
|
+
'version: 0.0.1\n' +
|
|
125
|
+
'rules:\n' +
|
|
126
|
+
' - >\n' +
|
|
127
|
+
bootstrap.split('\n').map((l) => ' ' + l).join('\n') + '\n';
|
|
128
|
+
|
|
129
|
+
// A Claude Code slash command: `/jikida-scan` in the editor kicks off the
|
|
130
|
+
// guided repo scan without the user remembering the phrasing.
|
|
131
|
+
const scanCommand =
|
|
132
|
+
'---\n' +
|
|
133
|
+
'description: Scan this repo for leaked secrets, exposed rules, and vulnerabilities (Jikida)\n' +
|
|
134
|
+
'---\n\n' +
|
|
135
|
+
'Read `.claude/skills/jikida/SKILL.md`, then run a full security pass on this\n' +
|
|
136
|
+
'repository: committed secrets (.env, serviceAccountKey.json, *.pem, API keys),\n' +
|
|
137
|
+
'open Supabase/Firebase rules, missing auth checks, injection, and out-of-date\n' +
|
|
138
|
+
'dependencies with known CVEs. Walk the 25-category catalog. Report findings\n' +
|
|
139
|
+
'worst-first with a one-line fix each, and apply fixes at the lowest safe\n' +
|
|
140
|
+
'intervention level. Ask before anything destructive. Record what you find in\n' +
|
|
141
|
+
'`memory-security.md`.\n';
|
|
142
|
+
|
|
143
|
+
// `managed: true` = a Jikida-owned file we fully (re)write on every run so
|
|
144
|
+
// updates land and re-running never appends a duplicate (SKILL.md, our own
|
|
145
|
+
// .mdc/command/continue files). The rest are shared assistant files we only
|
|
146
|
+
// append-if-missing so we never clobber a user's own rules.
|
|
120
147
|
const targets = [
|
|
121
|
-
[join(cwd, '.claude', 'skills', 'jikida', 'SKILL.md'), body],
|
|
148
|
+
[join(cwd, '.claude', 'skills', 'jikida', 'SKILL.md'), body, true],
|
|
149
|
+
[join(cwd, '.claude', 'commands', 'jikida-scan.md'), scanCommand, true],
|
|
150
|
+
[join(cwd, '.cursor', 'rules', 'jikida.mdc'), mdc, true],
|
|
151
|
+
[join(cwd, '.continue', 'config.yaml'), continueYaml, true],
|
|
122
152
|
[join(cwd, 'AGENTS.md'), bootstrap],
|
|
123
153
|
[join(cwd, 'CLAUDE.md'), bootstrap],
|
|
124
154
|
[join(cwd, 'GEMINI.md'), bootstrap],
|
|
125
155
|
[join(cwd, '.cursorrules'), bootstrap],
|
|
126
|
-
[join(cwd, '.cursor', 'rules', 'jikida.mdc'), mdc],
|
|
127
156
|
[join(cwd, '.windsurfrules'), bootstrap],
|
|
128
157
|
[join(cwd, '.clinerules'), bootstrap],
|
|
129
158
|
[join(cwd, '.aider.conf.yml'), bootstrap],
|
|
@@ -134,14 +163,14 @@ function installSkill() {
|
|
|
134
163
|
log(' ----------------------------');
|
|
135
164
|
const MARK = 'Jikida security skill';
|
|
136
165
|
let wrote = 0, skipped = 0;
|
|
137
|
-
for (const [target, content] of targets) {
|
|
166
|
+
for (const [target, content, managed] of targets) {
|
|
138
167
|
const rel = target.replace(cwd + '/', '');
|
|
139
168
|
try {
|
|
140
169
|
mkdirSync(dirname(target), { recursive: true });
|
|
141
|
-
//
|
|
142
|
-
// assistant files are APPEND-if-present,
|
|
143
|
-
// a user's
|
|
144
|
-
if (
|
|
170
|
+
// Managed (Jikida-owned) files are always (re)written so updates land and
|
|
171
|
+
// re-runs never duplicate. Shared assistant files are APPEND-if-present,
|
|
172
|
+
// skip-if-already-ours — never clobber a user's own rules, never double-add.
|
|
173
|
+
if (managed) {
|
|
145
174
|
writeFileSync(target, content);
|
|
146
175
|
done(rel); wrote++;
|
|
147
176
|
} else if (!existsSync(target)) {
|
|
@@ -157,11 +186,67 @@ function installSkill() {
|
|
|
157
186
|
warn(`Could not write ${rel}: ${e.message}`);
|
|
158
187
|
}
|
|
159
188
|
}
|
|
189
|
+
// Copy the bundled checklists (pre-deploy, supabase/firebase) next to the
|
|
190
|
+
// master skill so the assistant can walk them before a launch.
|
|
191
|
+
try {
|
|
192
|
+
const clSrc = join(here, '..', 'skill', 'checklists');
|
|
193
|
+
if (existsSync(clSrc)) {
|
|
194
|
+
const dstDir = join(cwd, '.claude', 'skills', 'jikida', 'checklists');
|
|
195
|
+
mkdirSync(dstDir, { recursive: true });
|
|
196
|
+
for (const f of readdirSync(clSrc)) {
|
|
197
|
+
writeFileSync(join(dstDir, f), readFileSync(join(clSrc, f), 'utf8'));
|
|
198
|
+
}
|
|
199
|
+
done('.claude/skills/jikida/checklists/'); wrote++;
|
|
200
|
+
}
|
|
201
|
+
} catch (e) { warn(`Could not copy checklists: ${e.message}`); }
|
|
202
|
+
|
|
203
|
+
// Harden .gitignore: a security skill install should also stop the classic
|
|
204
|
+
// leaks it scans for from ever being committed. Append only the lines that are
|
|
205
|
+
// missing, under one clearly-marked block — never duplicate, never reorder the
|
|
206
|
+
// user's file.
|
|
207
|
+
try {
|
|
208
|
+
const giPath = join(cwd, '.gitignore');
|
|
209
|
+
const current = existsSync(giPath) ? readFileSync(giPath, 'utf8') : '';
|
|
210
|
+
const wanted = [
|
|
211
|
+
'.env', '.env.*', '!.env.example',
|
|
212
|
+
'*.pem', '*.key', '*.p8', '*.p12', '*.keystore',
|
|
213
|
+
'serviceAccountKey.json', '*-firebase-adminsdk-*.json', 'google-services.json',
|
|
214
|
+
'*.mobileprovision', 'secrets.*', '.jikida/',
|
|
215
|
+
];
|
|
216
|
+
const missing = wanted.filter((line) => {
|
|
217
|
+
const re = new RegExp('^' + line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*$', 'm');
|
|
218
|
+
return !re.test(current);
|
|
219
|
+
});
|
|
220
|
+
if (missing.length) {
|
|
221
|
+
const block = (current && !current.endsWith('\n') ? '\n' : '')
|
|
222
|
+
+ '\n# Jikida security — do not commit secrets\n' + missing.join('\n') + '\n';
|
|
223
|
+
appendFileSync(giPath, block);
|
|
224
|
+
done(`.gitignore (+${missing.length} security entr${missing.length === 1 ? 'y' : 'ies'})`); wrote++;
|
|
225
|
+
}
|
|
226
|
+
} catch (e) { warn(`Could not update .gitignore: ${e.message}`); }
|
|
227
|
+
|
|
228
|
+
// A persistent findings log the assistant appends to across sessions, so a
|
|
229
|
+
// scan's results and the "still to fix" list survive context resets.
|
|
230
|
+
try {
|
|
231
|
+
const memPath = join(cwd, 'memory-security.md');
|
|
232
|
+
if (!existsSync(memPath)) {
|
|
233
|
+
writeFileSync(memPath,
|
|
234
|
+
'# Security memory (Jikida)\n\n' +
|
|
235
|
+
'The assistant appends findings and their status here as it scans and fixes.\n' +
|
|
236
|
+
'Read this at the start of a security pass so nothing is re-flagged or lost.\n\n' +
|
|
237
|
+
'## Open findings\n\n_none yet — run `/jikida-scan` or ask your AI to scan the repo._\n\n' +
|
|
238
|
+
'## Fixed\n\n');
|
|
239
|
+
done('memory-security.md'); wrote++;
|
|
240
|
+
}
|
|
241
|
+
} catch (e) { warn(`Could not write memory-security.md: ${e.message}`); }
|
|
242
|
+
|
|
160
243
|
if (wrote > 0 || skipped > 0) {
|
|
161
244
|
log('');
|
|
162
245
|
if (skipped > 0) { log(` ${skipped} file(s) already had the skill — left untouched.`); }
|
|
163
|
-
log(' Done. Your AI editors now know Jikida
|
|
164
|
-
log('
|
|
246
|
+
log(' Done. Your AI editors now know Jikida. Restart them, then:');
|
|
247
|
+
log('');
|
|
248
|
+
log(' ⚡ Run /jikida-scan in Claude Code — or ask any AI to');
|
|
249
|
+
log(' "scan my repo for leaked secrets and open rules".');
|
|
165
250
|
log('');
|
|
166
251
|
}
|
|
167
252
|
process.exit((wrote > 0 || skipped > 0) ? 0 : 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jikida/init",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "One command to install a production Web Application Firewall. Detects Node (Next.js / Express / Fastify), PHP (Laravel / Symfony), and Python (Django / FastAPI / Flask), installs the right Jikida SDK, wires the middleware, writes JIKIDA_TOKEN to .env. $0 to start. Fails open. Playground + MCP for Claude Code / Cursor / Windsurf included.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skill/SKILL.md
CHANGED
|
@@ -359,3 +359,10 @@ Default to the lowest level that fixes the finding. Anything destructive or irre
|
|
|
359
359
|
|
|
360
360
|
- **Score** — start at 100; subtract Critical 20 / High 10 / Medium 5 / Low 2, floored at 0. Grade: 90+ A, 70+ B, 50+ C, below 50 F. Non-applicable categories drop out and their weight redistributes.
|
|
361
361
|
- **Memory** — persist the score, accepted risks and custom rules across scans (in the app, or a local `memory-security.md` for the CLI). Re-runs show the trend and never re-flag an accepted risk. User preferences always win over a generic recommendation.
|
|
362
|
+
|
|
363
|
+
## Checklists
|
|
364
|
+
|
|
365
|
+
Concrete runbooks bundled with this skill — walk them before a launch:
|
|
366
|
+
|
|
367
|
+
- `checklists/pre-deploy.md` — the full pre-ship checklist across every category.
|
|
368
|
+
- `checklists/supabase-firebase.md` — the #1 vibe-coder leak (open RLS / test-mode rules); check before any launch.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Pre-deploy security checklist
|
|
2
|
+
|
|
3
|
+
Run this before every ship, merge, or "make the repo public". Each item maps to a Jikida check you can run with `npx @jikida/scan` or the MCP tools. Fix at the lowest safe level; ask before anything destructive.
|
|
4
|
+
|
|
5
|
+
## Secrets
|
|
6
|
+
- [ ] No API keys, tokens, or passwords committed (grep the diff; run a repo scan).
|
|
7
|
+
- [ ] `.env`, `serviceAccountKey.json`, `*.pem`, `*.db` are in `.gitignore` and NOT tracked.
|
|
8
|
+
- [ ] No `service_role`, admin, or private key used in client-side code or shipped in the bundle.
|
|
9
|
+
- [ ] Rotate anything that was ever committed — removing it from HEAD is not enough.
|
|
10
|
+
|
|
11
|
+
## Database
|
|
12
|
+
- [ ] Supabase RLS is ON for every table holding user data (default-deny, then explicit policies).
|
|
13
|
+
- [ ] Firebase rules are not `allow read, write: if true` (test mode).
|
|
14
|
+
- [ ] No endpoint trusts a client-supplied `userId` / `ownerId` (IDOR — scope by the session).
|
|
15
|
+
- [ ] PII columns are encrypted or access-controlled.
|
|
16
|
+
|
|
17
|
+
## Auth & sessions
|
|
18
|
+
- [ ] Session cookies are `HttpOnly`, `Secure`, `SameSite`.
|
|
19
|
+
- [ ] Login and password-reset have brute-force limits.
|
|
20
|
+
- [ ] JWTs verify the signature and reject `alg=none`; tokens have an expiry.
|
|
21
|
+
|
|
22
|
+
## Transport & headers
|
|
23
|
+
- [ ] HTTPS only; HSTS set.
|
|
24
|
+
- [ ] CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy present.
|
|
25
|
+
- [ ] CORS is an explicit origin allowlist — never `*` with credentials.
|
|
26
|
+
|
|
27
|
+
## Input & injection
|
|
28
|
+
- [ ] All queries are parameterized (no string-built SQL / NoSQL).
|
|
29
|
+
- [ ] File uploads check real type (magic bytes), size, and a safe path.
|
|
30
|
+
- [ ] User input rendered in HTML is escaped; no `dangerouslySetInnerHTML` on untrusted data.
|
|
31
|
+
|
|
32
|
+
## Dependencies & config
|
|
33
|
+
- [ ] No dependency with a known CVE (run the supply-chain check).
|
|
34
|
+
- [ ] Lockfile committed and up to date.
|
|
35
|
+
- [ ] Debug mode / verbose errors OFF in production.
|
|
36
|
+
- [ ] Rate limiting on auth, write, and expensive endpoints.
|
|
37
|
+
|
|
38
|
+
## After deploy
|
|
39
|
+
- [ ] Uptime monitor + SSL/domain-expiry watch enabled on the live URL.
|
|
40
|
+
- [ ] A surface pentest run against the deployed site comes back clean or triaged.
|
|
41
|
+
|
|
42
|
+
Run it all at once:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
$ npx @jikida/scan yourdomain.com --repo . --min-score 80
|
|
46
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Supabase & Firebase checklist
|
|
2
|
+
|
|
3
|
+
The single most common way an AI-built app leaks its whole database. Check this before any launch.
|
|
4
|
+
|
|
5
|
+
## Supabase
|
|
6
|
+
- [ ] **RLS is enabled on every table with user data.** A table with RLS off is world-readable through the anon key.
|
|
7
|
+
```sql
|
|
8
|
+
-- 🔴 CRITICAL — RLS off: anyone with the public anon key reads every row
|
|
9
|
+
-- 🟢 CORRECT — enable RLS, then add explicit policies
|
|
10
|
+
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
|
11
|
+
CREATE POLICY "own rows" ON public.profiles
|
|
12
|
+
FOR SELECT USING (auth.uid() = user_id);
|
|
13
|
+
```
|
|
14
|
+
- [ ] Policies are default-deny — a table with RLS on but no policy returns nothing, which is safe; a permissive `USING (true)` policy is not.
|
|
15
|
+
- [ ] The `service_role` key is used ONLY on the server, never in client code or the bundle.
|
|
16
|
+
- [ ] Storage buckets holding private files are not public.
|
|
17
|
+
- [ ] Postgres functions marked `SECURITY DEFINER` are reviewed — they bypass RLS.
|
|
18
|
+
|
|
19
|
+
## Firebase
|
|
20
|
+
- [ ] Firestore/RTDB rules are not left in test mode.
|
|
21
|
+
```
|
|
22
|
+
// 🔴 CRITICAL — test mode: the whole database is public read/write
|
|
23
|
+
match /{document=**} { allow read, write: if true; }
|
|
24
|
+
// 🟢 CORRECT — require auth and scope to the owner
|
|
25
|
+
match /users/{uid}/{doc=**} {
|
|
26
|
+
allow read, write: if request.auth != null && request.auth.uid == uid;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
- [ ] `google-services.json` / `serviceAccountKey.json` is not committed.
|
|
30
|
+
- [ ] Client API keys are restricted (referrer / bundle-id / API restrictions in the Google console) — a client key is public, so the rules are the real gate.
|
|
31
|
+
- [ ] Cloud Functions validate `context.auth` before any privileged action.
|
|
32
|
+
- [ ] Storage rules mirror the database rules — a locked DB with an open bucket still leaks.
|
|
33
|
+
|
|
34
|
+
Run the automated version:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
$ npx @jikida/scan --repo . # flags open RLS/rules, committed keys, service_role in client
|
|
38
|
+
```
|