@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 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
- // The master SKILL.md is always (re)written so updates land. The per-
142
- // assistant files are APPEND-if-present, skip-if-already-ours — never clobber
143
- // a user's existing rules file, never double-add.
144
- if (target.endsWith('SKILL.md')) {
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 restart them, then ask them to');
164
- log(' "add Jikida security to this app" or "scan my repo for leaked secrets".');
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.2.1",
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
+ ```