ilumin-cli 1.1.1 → 1.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-add.d.ts","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH,UAAU,aAAa;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAuEtE"}
1
+ {"version":3,"file":"mcp-add.d.ts","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,UAAU,aAAa;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CA8DtE"}
@@ -11,7 +11,6 @@ import { installClaudeDesktop } from "../clients/claude-desktop.js";
11
11
  import { installGemini } from "../clients/gemini.js";
12
12
  import { installCursor } from "../clients/cursor.js";
13
13
  import { installCodex } from "../clients/codex.js";
14
- import { installSkillsForClient } from "../utils/install-skills.js";
15
14
  // Clientes disponíveis com metadados
16
15
  const SUPPORTED_CLIENTS = {
17
16
  "claude-code": { label: "Claude Code" },
@@ -67,15 +66,6 @@ export function runMcpAdd(server, options) {
67
66
  hasError = true;
68
67
  const msg = err instanceof Error ? err.message : String(err);
69
68
  printError(`${label}: ${msg}`);
70
- continue;
71
- }
72
- try {
73
- const skillsDir = installSkillsForClient(client);
74
- printSuccess(`Skills instaladas em ${skillsDir}`);
75
- }
76
- catch (err) {
77
- const msg = err instanceof Error ? err.message : String(err);
78
- printWarning(`${label}: skills não instaladas — ${msg}`);
79
69
  }
80
70
  }
81
71
  console.log();
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-add.js","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-add.js","sourceRoot":"","sources":["../../src/commands/mcp-add.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ import { spawnSync } from "child_process";
10
10
  import kleur from "kleur";
11
11
  import { printInfo, printSuccess, printError } from "../utils/logger.js";
12
12
  export function runSkillsInstall(options) {
13
- const repo = "IluminClients/ilumin-cli";
13
+ const repo = "IluminClients/ilumin-skills";
14
14
  const args = ["skills", "add", repo, "-g"];
15
15
  if (options.agent) {
16
16
  for (const agent of options.agent.split(",").map(a => a.trim())) {
@@ -1 +1 @@
1
- {"version":3,"file":"skills-install.js","sourceRoot":"","sources":["../../src/commands/skills-install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMzE,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC1D,MAAM,IAAI,GAAG,0BAA0B,CAAC;IAExC,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,wCAAwC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;QAClC,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACf,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,UAAU,CAAC,oEAAoE,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,YAAY,CAAC,0CAA0C,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"skills-install.js","sourceRoot":"","sources":["../../src/commands/skills-install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMzE,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC1D,MAAM,IAAI,GAAG,6BAA6B,CAAC;IAE3C,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,wCAAwC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;QAClC,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACf,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,UAAU,CAAC,oEAAoE,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,YAAY,CAAC,0CAA0C,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilumin-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Ilumin Cloud CLI — instala e gerencia o MCP da Ilumin em vários clientes de IA",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,13 +9,12 @@
9
9
  "ilumin-cli": "dist/index.js"
10
10
  },
11
11
  "scripts": {
12
- "build": "tsc && chmod +x dist/index.js && node scripts/copy-skills.js",
12
+ "build": "tsc && chmod +x dist/index.js",
13
13
  "dev": "tsc --watch",
14
14
  "start": "node dist/index.js"
15
15
  },
16
16
  "files": [
17
- "dist",
18
- "skills"
17
+ "dist"
19
18
  ],
20
19
  "dependencies": {
21
20
  "commander": "^14.0.2",
@@ -1,217 +0,0 @@
1
- ---
2
- name: ilumin-cloud-developer
3
- description: Use this skill when managing deployments, servers, custom domains, or installing and integrating applications using the Ilumin Cloud Model Context Protocol (MCP). Useful for building code, deploying, updating, and setting up catalog integrations like WhatsApp, databases, queues, and more.
4
- ---
5
-
6
- # Ilumin Cloud Developer
7
-
8
- This skill provides the comprehensive workflow, tools, best practices, and contextual knowledge for interacting with Ilumin Cloud infrastructure via the Ilumin MCP Server.
9
-
10
- ## What is Ilumin Cloud?
11
-
12
- **Ilumin Cloud** is a Brazilian PaaS (Platform as a Service) focused on simplifying self-hosted application deployment. It provides:
13
-
14
- - **Managed VPS Servers** — The user has one or more cloud servers (e.g., `srv1.user.ilumin.app`) pre-configured with Docker, Traefik (reverse proxy), and SSL termination.
15
- - **App Catalog** — A curated collection of ready-to-deploy open-source applications (databases, messaging APIs, automation tools, etc.) that can be installed with a single command — no Docker knowledge required from the user.
16
- - **CI/CD Pipeline** — Users can push their own source code, which is built via Docker into an image, versioned with a timestamp tag, and deployed as a container.
17
- - **Automatic HTTPS** — Every deployed app automatically gets a subdomain with SSL (e.g., `myapp.srv1.user.ilumin.app`), and custom domains can also be pointed via CNAME.
18
- - **Traefik Routing** — All traffic is routed by a Traefik reverse proxy running on the server. Compose files must follow Ilumin's label conventions for routing to work correctly.
19
-
20
- The Ilumin MCP connects AI agents directly to this infrastructure, enabling autonomous deployment, integration, and maintenance operations without requiring the user to use SSH or the Ilumin web dashboard manually.
21
-
22
- ---
23
-
24
- ## Common Use Cases
25
-
26
- Understanding real-world scenarios where this skill is most valuable:
27
-
28
- ### Deploying a Custom Application
29
- The user has a web app (Node.js, Python/FastAPI, Next.js, etc.) in a repository and wants it live on the internet. The AI uses `deploy_project` to build and push it, then `install_app` to deploy it for the first time, or `update_app` for subsequent deployments.
30
-
31
- ### Adding WhatsApp to a Project
32
- The user wants to send/receive WhatsApp messages in their app. The AI installs the **Evolution API** from the catalog, then uses `get_catalog_app_documentation` to obtain its endpoint and API key, and writes the integration code into the user's project.
33
-
34
- ### Adding a Database
35
- The user needs a PostgreSQL or Redis instance. The AI finds the appropriate app in the catalog and installs it on the same server, exposing the internal connection string for the user's application to consume.
36
-
37
- ### Integrating Automation Workflows
38
- The user wants to automate tasks (email, webhooks, AI pipelines). The AI installs **n8n** or a similar tool from the catalog, which then connects to the user's app via webhooks or APIs.
39
-
40
- ### Connecting a Custom Domain
41
- After deploying an app, the user wants it accessible at `app.mycompany.com` instead of the Ilumin subdomain. The AI uses `manage_domain` and instructs the user on configuring the CNAME at their DNS provider.
42
-
43
- ### Rolling Updates Without Downtime
44
- The user has pushed new code. The AI runs `deploy_project` to build the new image, captures the timestamp tag, and runs `update_app` with the specific tag, ensuring Docker pulls the fresh layer and not a cached one.
45
-
46
- ---
47
-
48
- ## App Catalog Overview
49
-
50
- The catalog contains popular open-source services. When the user's need matches one of these, always prefer installing from the catalog instead of building from scratch:
51
-
52
- | Category | Examples |
53
- |---|---|
54
- | **Messaging / WhatsApp** | Evolution API, Chatwoot |
55
- | **Automation / Workflows** | n8n, Typebot |
56
- | **Databases** | PostgreSQL, MySQL, Redis, MongoDB |
57
- | **AI / LLMs** | Open WebUI, Flowise |
58
- | **CMS / Web** | WordPress, Ghost, Strapi |
59
- | **Analytics** | Metabase, Plausible |
60
- | **Storage** | MinIO |
61
- | **Email** | Mailu, Stalwart |
62
-
63
- > Always run `list_catalog` to get the current up-to-date list and check if a specific app is available before suggesting a custom implementation.
64
-
65
- ---
66
-
67
- ## Architecture Reference
68
-
69
- Understanding Ilumin's infrastructure prevents misconfiguration:
70
-
71
- ```
72
- [Internet] → [Traefik (reverse proxy on server)] → [Docker Containers]
73
-
74
- Reads routing rules from Docker labels
75
- Handles SSL termination automatically
76
- ```
77
-
78
- - **Each app is a Docker container** with a `docker-compose.yml` defining the service, network, and Traefik labels.
79
- - **All containers** must be on the `network: ilumin-network` (or `external: true`) for Traefik to discover them and for containers to communicate internally.
80
- - **Internal container-to-container communication** uses the container/service name as the hostname (e.g., a Python app connects to `postgres-service:5432` on the shared network).
81
- - **`get_compose_guidelines`** returns the exact label templates and network definitions required — always use it before writing a compose file.
82
-
83
- ---
84
-
85
- ## Available MCP Tools
86
-
87
- The Ilumin MCP server exposes specialized tools for the complete application lifecycle. Prioritize these whenever the objective involves Ilumin Cloud resources:
88
-
89
- ### Information & Discovery
90
- - **`list_servers`** — Discover the user's available servers, domains, and currently active applications. *Always use this to verify the environment state before proposing an installation.*
91
- - **`list_deploys`** — Check historical build logs and image tags of past deployments.
92
- - **`list_catalog`** — List official and community applications available for 1-click installation.
93
-
94
- ### Building & Versioning
95
- - **`deploy_project`** — Zip and push the current project source code, triggering a Docker build in the cloud. Returns a specific image tag (timestamp format, e.g., `20260407130809`) on success.
96
- - **`update_app`** — Update an existing application to a new version. **IMPORTANT**: Always use specific timestamp tags instead of `latest` to force Docker to pull the fresh image rather than using cached layers.
97
-
98
- ### Installation & Configuration
99
- - **`install_app`** — Install a custom app onto a server for the first time. Requires a `docker-compose.yml` string formatted to Ilumin standards.
100
- - **`get_compose_guidelines`** — **MANDATORY** before generating any Docker Compose file. Returns the Traefik labels, network standards, and variable placeholders required for correct domain mapping.
101
- - **`manage_domain`** — Point a custom external domain (e.g., `app.mysite.com`) to an application. Always remind the user to create a CNAME at their DNS provider pointing to the server's primary domain.
102
- - **`get_app_env`** — Fetches the detailed environment variables (ENVs) for a specific application. Essential when an AI needs to retrieve credentials (like an API Key, database password, or URL) natively configured in the application environment (e.g., retrieving Evolution API credentials).
103
-
104
-
105
- ### Catalog & Integrations
106
- - **`install_catalog_app`** — Install a complete application from the catalog without building source code (e.g., installing a WhatsApp API or database).
107
- - **`get_catalog_app_documentation`** — **MANDATORY** after installing a catalog app (or when integrating an existing one). Fetches connection data, API structure, and context needed to write integration code correctly. If this fails, fall back to Context7 or web search.
108
-
109
- ---
110
-
111
- ## Core Best Practices and Workflows
112
-
113
- ### 1. The "Memory" File (`ilumin.md`)
114
-
115
- Always act as the primary state manager for the project's deployment.
116
-
117
- - **Always maintain a file named `ilumin.md` in the user's project root.**
118
- - **Record**: Every successful installation, specific timestamp image tags used, the server domain (e.g., `srv1.user.ilumin.app`), custom domains configured, environment variables set, and any architectural notes.
119
- - **Before any action**: Check `ilumin.md` first to determine whether to call `install_app` (first time) or `update_app` (subsequent deployments) — this prevents destructive duplicate installations.
120
-
121
- ### 2. Proactive Application Integrations Workflow
122
-
123
- When the user asks to build features requiring complex backend services (WhatsApp, queues, databases, automation), follow this pattern:
124
-
125
- 1. Run `list_catalog` to check if an off-the-shelf app fulfills the requirement.
126
- 2. Present the finding to the user and request permission to install it.
127
- 3. If approved, run `install_catalog_app` to deploy the integration.
128
- 4. Run `get_catalog_app_documentation` with the app's slug to learn the exact integration endpoints, credentials, and variables exposed.
129
- 5. Use that documentation to write the precise integration code inside the user's main project.
130
- 6. Record the installation details in `ilumin.md`.
131
-
132
- ### 3. New Application Deployment Workflow
133
-
134
- 1. Check `ilumin.md` and/or `list_servers` to confirm the app doesn't already exist.
135
- 2. Run `deploy_project` to build the Docker image and obtain the image tag.
136
- 3. Run `get_compose_guidelines` to learn the Traefik standards and network config.
137
- 4. Run `install_app` using the obtained image tag and the generated compose file.
138
- 5. Record the server, tag, app name, and URL in `ilumin.md`.
139
-
140
- ### 4. Sending Environment Variables (ENVs) to Apps
141
-
142
- **A) Catalog Apps — only the 4 predefined variables**
143
-
144
- Catalog apps have their `docker-compose.yml` pre-built by the platform. The only ENVs you can pass are the 4 standard platform variables — and only the ones the app actually requires (as listed by `list_catalog`):
145
-
146
- - `APP_USER` — username/login
147
- - `APP_PASSWORD` — password
148
- - `APP_EMAIL` — admin email
149
- - `APP_API_KEY` — api key or token
150
-
151
- Pass them via `envVars` in `install_catalog_app`. You **cannot** send arbitrary custom ENV names — they will be ignored. Always ask the user to provide the values; never invent them.
152
-
153
- ```
154
- install_catalog_app(
155
- serverDomain: "srv1.user.com",
156
- slug: "waha",
157
- envVars: {
158
- "APP_USER": "admin",
159
- "APP_PASSWORD": "strongpassword"
160
- }
161
- )
162
- ```
163
-
164
- **B) Custom Apps — any ENVs via the compose `environment` block**
165
-
166
- When using `install_app` with a custom `docker-compose.yml`, you can freely define any environment variables needed by the application in the `environment` section:
167
-
168
- ```yaml
169
- services:
170
- my-app:
171
- image: ghcr.io/user/my-app:20260407130809
172
- environment:
173
- DATABASE_URL: "postgresql://user:pass@postgres:5432/mydb"
174
- SECRET_KEY: "my-secret"
175
- DEBUG: "false"
176
- ```
177
-
178
- Always ask the user for sensitive values before writing them into the compose. Never hardcode guessed values.
179
-
180
- **C) Reading ENVs from an Existing App — via `get_app_env`**
181
-
182
- If the AI needs credentials from an already-installed app (e.g., the Evolution API key or a database password), use `get_app_env` to fetch the live ENV values directly. This avoids asking the user for information they may not remember.
183
-
184
- ### 4. Updating an Existing Application
185
-
186
- 1. Verify it exists via `list_servers` or `ilumin.md`.
187
- 2. Run `deploy_project` to commit changes and obtain the new timestamp version tag (e.g., `20260407130809`).
188
- 3. Run `update_app` supplying the `app_name` and the new specific version tag. **Never use `latest`.**
189
- 4. Update `ilumin.md` with the new tag and date.
190
-
191
- ### 5. Custom Domain Configuration
192
-
193
- When using `manage_domain`, always:
194
- - Explicitly inform the user to create a **CNAME record** at their DNS provider pointing their custom domain to the server's primary domain (e.g., `srv1.user.ilumin.app`).
195
- - Check `ilumin.md` to verify if a custom domain is already established, preventing accidental overwrites without user confirmation.
196
- - Note that DNS propagation can take up to 24–48 hours, though it's typically much faster.
197
-
198
- ---
199
-
200
- ## Common Errors and Troubleshooting
201
-
202
- | Symptom | Likely Cause | Resolution |
203
- |---|---|---|
204
- | App unreachable after install | Missing or incorrect Traefik labels | Re-run `get_compose_guidelines`, regenerate compose file |
205
- | Docker pulls old image after update | Used `latest` tag instead of specific tag | Always use timestamp tags from `deploy_project` output |
206
- | Container exits immediately | Missing required env var or misconfigured port | Review compose file; check logs via server SSH or Ilumin dashboard |
207
- | Custom domain returns 404 | CNAME not yet propagated or `manage_domain` not called | Verify DNS, re-run `manage_domain` if needed |
208
- | `install_app` fails with "already exists" | App installed previously | Use `update_app` instead |
209
- | Catalog app missing credentials | Env vars not passed during `install_catalog_app` | Check `list_catalog` for required `env_vars`, provide them |
210
- | Inter-container connection refused | Containers on different networks | Ensure all services share `ilumin-network` as an external network |
211
-
212
- ---
213
-
214
- ## Additional Resources
215
-
216
- - For Docker Compose formatting rules, Traefik labels, network configuration, and reference examples, see [references/compose.md](references/compose.md)
217
- - For the recommended default stack (NocoDB, Redis, BullMQ) when the user hasn't specified infrastructure, see [references/recommended-stack.md](references/recommended-stack.md)
@@ -1,425 +0,0 @@
1
- # Ilumin Compose Formatter
2
-
3
- This skill defines the mandatory rules and patterns for writing `docker-compose.yml` files compatible with the Ilumin Cloud platform. Follow every rule — small mistakes like a fixed router name or a wrong network definition are the most common causes of deployment failures.
4
-
5
- ---
6
-
7
- ## How Ilumin's Infrastructure Works
8
-
9
- Understanding the infrastructure prevents 90% of errors:
10
-
11
- ```
12
- [Internet] → [Traefik v3 on port 80/443] → [Docker containers via labels]
13
-
14
- Reads routing rules from labels
15
- Handles SSL via Let's Encrypt
16
- Routes by hostname or path
17
- ```
18
-
19
- - Traefik runs as a separate container on the `traefik` **external** Docker network.
20
- - All app containers that need public access must join the `traefik` network.
21
- - Containers that must stay private (databases, caches) must join only the `internal` network.
22
- - Containers on the same `internal` network communicate using the **service name** as hostname (e.g., `postgres:5432`).
23
-
24
- ---
25
-
26
- ## Mandatory Formatting Rules
27
-
28
- ### 1. No Comments
29
- Remove **all** comments (lines starting with `#`) from the final compose file.
30
-
31
- ### 2. No Resource Limits
32
- Remove any `deploy`, `resources`, `limits`, or `reservations` sections — Ilumin manages these at the infrastructure level.
33
-
34
- ### 3. Image Version Variable
35
- Replace the image tag of the **main application** with `${APP_VERSION}`.
36
-
37
- ```yml
38
- # ❌ Wrong
39
- image: n8n:latest
40
- image: ghost:5.82.2
41
- image: myapp:v2.1.0
42
-
43
- # ✅ Correct
44
- image: n8n:${APP_VERSION}
45
- image: ghost:${APP_VERSION}
46
- image: myapp:${APP_VERSION}
47
- ```
48
-
49
- > **Critical:** `APP_VERSION` replaces the **entire tag** after the colon, including any `v` prefix. Never write `v${APP_VERSION}`.
50
-
51
- ### 4. Database Password Variable
52
- Replace **all** database passwords with `${DB_PASSWORD}`:
53
-
54
- ```yml
55
- # Applies to: POSTGRES_PASSWORD, MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD, MARIADB_ROOT_PASSWORD, etc.
56
- - POSTGRES_PASSWORD=${DB_PASSWORD}
57
- - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
58
- - MYSQL_PASSWORD=${DB_PASSWORD}
59
- ```
60
-
61
- ### 5. Optional Admin Credentials
62
- If the app supports initial admin setup, use these standard variables:
63
- - `${APP_USERNAME}` — admin username
64
- - `${APP_PASSWORD}` — admin password
65
- - `${APP_EMAIL}` — admin email
66
-
67
- ### 6. No Backslashes in URLs
68
- Never use `\` before `$` in URLs inside the compose file.
69
-
70
- ```yml
71
- # ❌ Wrong
72
- - APP_URL=https://\${BASE_DOMAIN}
73
-
74
- # ✅ Correct
75
- - APP_URL=https://${BASE_DOMAIN}
76
- ```
77
-
78
- ---
79
-
80
- ## Network Rules (Critical)
81
-
82
- ```yml
83
- # ✅ Main app service (public-facing) — both networks
84
- networks:
85
- - traefik
86
- - internal
87
-
88
- # ✅ Dependency services (db, redis, queue) — internal only
89
- networks:
90
- - internal
91
- ```
92
-
93
- **Always define networks at the bottom of the file exactly like this:**
94
-
95
- ```yml
96
- networks:
97
- traefik:
98
- external: true
99
- internal:
100
- ```
101
-
102
- > ⚠️ The `traefik` network must be `external: true`. Forgetting this causes Traefik to be unable to discover the container.
103
-
104
- ---
105
-
106
- ## Traefik Labels (Critical)
107
-
108
- Every public-facing service needs these labels. **Copy this block exactly** and replace `<appname>` with a unique identifier for the app.
109
-
110
- ```yml
111
- labels:
112
- - traefik.enable=true
113
- - traefik.docker.network=traefik
114
- - traefik.http.routers.<appname>.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
115
- - traefik.http.routers.<appname>.entrypoints=websecure
116
- - traefik.http.routers.<appname>.tls=true
117
- - traefik.http.routers.<appname>.tls.certresolver=letsencrypt
118
- - traefik.http.services.<appname>.loadbalancer.server.port=<PORT>
119
- ```
120
-
121
- ### Router Naming Rules (Most Common Error)
122
-
123
- The router/service name in Traefik labels **must be unique across all apps on the server**. Using generic names like `backend` or `frontend` causes conflicts when multiple apps are deployed.
124
-
125
- **Formula:** `{appname}{role}` — always prefix with the app name.
126
-
127
- ```yml
128
- # ❌ WRONG — will conflict with any other app using the same generic name
129
- - traefik.http.routers.backend.rule=...
130
- - traefik.http.routers.frontend.rule=...
131
-
132
- # ✅ CORRECT — unique per app
133
- - traefik.http.routers.n8nmain.rule=...
134
- - traefik.http.routers.ghostblog.rule=...
135
- - traefik.http.routers.myappfrontend.rule=...
136
- - traefik.http.routers.myappbackend.rule=...
137
- ```
138
-
139
- ### Domain Rule Syntax
140
-
141
- The domain rule must use this exact syntax — it supports both the Ilumin base domain and an optional custom domain:
142
-
143
- ```
144
- Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
145
- ```
146
-
147
- > **Why this syntax?** `${CUSTOM_DOMAIN:+ || Host(...)}` is a shell parameter expansion that only adds the custom domain rule if the variable is set. This allows the platform to manage domain routing dynamically without requiring a compose file change.
148
-
149
- ---
150
-
151
- ## Multi-Service Compose Rules
152
-
153
- ### Two Services, One Domain (Path Routing — Frontend + Backend)
154
-
155
- When your app has a frontend and a backend on the **same domain**:
156
-
157
- ```yml
158
- # Frontend: answers on / (no path prefix needed)
159
- labels:
160
- - traefik.http.routers.myappfrontend.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
161
- - traefik.http.services.myappfrontend.loadbalancer.server.port=3000
162
-
163
- # Backend: answers on /api
164
- labels:
165
- - traefik.http.routers.myappbackend.rule=(Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}) && PathPrefix(`/api`)
166
- - traefik.http.services.myappbackend.loadbalancer.server.port=8000
167
- ```
168
-
169
- ### Two Services, Two Subdomains
170
-
171
- When you need separate subdomains for backend and frontend:
172
-
173
- ```yml
174
- # Frontend — base domain
175
- labels:
176
- - traefik.http.routers.myappfrontend.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
177
-
178
- # Backend — api subdomain prefix
179
- labels:
180
- - traefik.http.routers.myappbackend.rule=Host(`api.${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`api.${CUSTOM_DOMAIN}`)}
181
-
182
- # Other services follow the same prefix pattern:
183
- # db.${BASE_DOMAIN}, admin.${BASE_DOMAIN}, etc.
184
- ```
185
-
186
- ---
187
-
188
- ## HTTP → HTTPS Redirect (Optional but Recommended)
189
-
190
- To redirect HTTP traffic to HTTPS, add these labels alongside the main router:
191
-
192
- ```yml
193
- labels:
194
- # ... (main HTTPS router labels above) ...
195
-
196
- # HTTP redirect router
197
- - traefik.http.routers.<appname>-http.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
198
- - traefik.http.routers.<appname>-http.entrypoints=web
199
- - traefik.http.routers.<appname>-http.middlewares=redirect-to-https
200
- - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
201
- ```
202
-
203
- ---
204
-
205
- ## Architecture Options
206
-
207
- ### Option 1: Unified Build (Recommended) ✅
208
-
209
- Serve the compiled frontend through the backend. One container, no CORS issues.
210
-
211
- - Build the frontend in Stage 1 of the Dockerfile.
212
- - Copy `dist/` into the backend Stage 2.
213
- - Backend serves static files from `/` and API from `/api`.
214
- - Only one Traefik router needed.
215
-
216
- ### Option 2: Separate Containers
217
-
218
- Use path routing or subdomains. Requires proper CORS config on the backend.
219
-
220
- > The browser **cannot** access `http://backend:8000` — the Docker internal network is not accessible from the user's browser. The frontend must call the public domain (e.g., `/api` relative path or `https://api.domain.com`).
221
-
222
- ---
223
-
224
- ## Common Errors and Fixes
225
-
226
- | Error | Cause | Fix |
227
- |---|---|---|
228
- | App unreachable (502 Bad Gateway) | Wrong port in `loadbalancer.server.port` | Set the port the container actually listens on |
229
- | App unreachable (no SSL / cert error) | Missing `tls=true` or `certresolver=letsencrypt` | Add both TLS labels |
230
- | Two apps conflict (first works, second doesn't) | Duplicate router names in Traefik labels | Use unique names: `{appname}{role}` |
231
- | Database not reachable from app | DB container not on `internal` network | Ensure both app and DB are on `internal` |
232
- | Traefik can't discover container | `traefik` network not set as `external: true` | Fix the network definition at the bottom |
233
- | URL contains `v${APP_VERSION}` | Manually adding `v` before the variable | Remove the `v` — `APP_VERSION` already includes it |
234
- | URL contains backslash `\$` | Escaping `$` in compose | Remove `\` — dollar signs don't need escaping in compose YAML |
235
- | Custom domain not working | `manage_domain` not called after install | Call `manage_domain` and configure CNAME at DNS provider |
236
-
237
- ---
238
-
239
- ## Reference Examples
240
-
241
- ### Minimal Single App (Uptime Kuma)
242
-
243
- ```yml
244
- services:
245
- uptime-kuma:
246
- image: louislam/uptime-kuma:${APP_VERSION}
247
- volumes:
248
- - uptime_data:/app/data
249
- networks:
250
- - traefik
251
- - internal
252
- labels:
253
- - traefik.enable=true
254
- - traefik.docker.network=traefik
255
- - traefik.http.routers.uptimekuma.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
256
- - traefik.http.routers.uptimekuma.entrypoints=websecure
257
- - traefik.http.routers.uptimekuma.tls=true
258
- - traefik.http.routers.uptimekuma.tls.certresolver=letsencrypt
259
- - traefik.http.services.uptimekuma.loadbalancer.server.port=3001
260
- restart: unless-stopped
261
-
262
- volumes:
263
- uptime_data:
264
-
265
- networks:
266
- traefik:
267
- external: true
268
- internal:
269
- ```
270
-
271
- ### App + Database (WordPress + MySQL)
272
-
273
- ```yml
274
- services:
275
- wordpress:
276
- image: wordpress:${APP_VERSION}
277
- depends_on:
278
- - mysql
279
- environment:
280
- - WORDPRESS_DB_HOST=mysql
281
- - WORDPRESS_DB_USER=wordpress
282
- - WORDPRESS_DB_PASSWORD=${DB_PASSWORD}
283
- - WORDPRESS_DB_NAME=wordpress
284
- volumes:
285
- - wordpress_data:/var/www/html
286
- networks:
287
- - traefik
288
- - internal
289
- labels:
290
- - traefik.enable=true
291
- - traefik.docker.network=traefik
292
- - traefik.http.routers.wordpress.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
293
- - traefik.http.routers.wordpress.entrypoints=websecure
294
- - traefik.http.routers.wordpress.tls=true
295
- - traefik.http.routers.wordpress.tls.certresolver=letsencrypt
296
- - traefik.http.services.wordpress.loadbalancer.server.port=80
297
- restart: unless-stopped
298
-
299
- mysql:
300
- image: mysql:5.7
301
- environment:
302
- - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
303
- - MYSQL_DATABASE=wordpress
304
- - MYSQL_USER=wordpress
305
- - MYSQL_PASSWORD=${DB_PASSWORD}
306
- volumes:
307
- - mysql_data:/var/lib/mysql
308
- networks:
309
- - internal
310
- restart: unless-stopped
311
-
312
- volumes:
313
- wordpress_data:
314
- mysql_data:
315
-
316
- networks:
317
- traefik:
318
- external: true
319
- internal:
320
- ```
321
-
322
- ### App + Database (Baserow + PostgreSQL)
323
-
324
- ```yml
325
- services:
326
- baserow:
327
- image: baserow/baserow:${APP_VERSION}
328
- depends_on:
329
- - postgres
330
- volumes:
331
- - baserow_data:/baserow/data
332
- environment:
333
- - BASEROW_PUBLIC_URL=https://${BASE_DOMAIN}
334
- - DATABASE_HOST=postgres
335
- - DATABASE_NAME=baserow
336
- - DATABASE_USER=postgres
337
- - DATABASE_PASSWORD=${DB_PASSWORD}
338
- networks:
339
- - traefik
340
- - internal
341
- labels:
342
- - traefik.enable=true
343
- - traefik.docker.network=traefik
344
- - traefik.http.routers.baserow.rule=Host(`${BASE_DOMAIN}`)${CUSTOM_DOMAIN:+ || Host(`${CUSTOM_DOMAIN}`)}
345
- - traefik.http.routers.baserow.entrypoints=websecure
346
- - traefik.http.routers.baserow.tls=true
347
- - traefik.http.routers.baserow.tls.certresolver=letsencrypt
348
- - traefik.http.routers.baserow.service=baserow
349
- - traefik.http.services.baserow.loadbalancer.server.port=80
350
- restart: unless-stopped
351
-
352
- postgres:
353
- image: postgres:16
354
- environment:
355
- - POSTGRES_USER=postgres
356
- - POSTGRES_PASSWORD=${DB_PASSWORD}
357
- - POSTGRES_DB=baserow
358
- volumes:
359
- - postgres_data:/var/lib/postgresql/data
360
- networks:
361
- - internal
362
- restart: unless-stopped
363
-
364
- volumes:
365
- baserow_data:
366
- postgres_data:
367
-
368
- networks:
369
- traefik:
370
- external: true
371
- internal:
372
- ```
373
-
374
- ### Custom App (Fixed Domain, No Variables)
375
-
376
- Used when the user wants to hardcode a specific domain. Inform the user that Ilumin's domain management panel will NOT work in this mode — they must manage DNS manually.
377
-
378
- ```yml
379
- services:
380
- app:
381
- image: myapp:${APP_VERSION}
382
- container_name: myapp
383
- restart: unless-stopped
384
- environment:
385
- - NODE_ENV=production
386
- networks:
387
- - traefik
388
- - internal
389
- labels:
390
- - traefik.enable=true
391
- - traefik.docker.network=traefik
392
- - traefik.http.routers.myapp.rule=Host(`app.mycompany.com`)
393
- - traefik.http.routers.myapp.entrypoints=websecure
394
- - traefik.http.routers.myapp.tls=true
395
- - traefik.http.routers.myapp.tls.certresolver=letsencrypt
396
- - traefik.http.routers.myapp-http.rule=Host(`app.mycompany.com`)
397
- - traefik.http.routers.myapp-http.entrypoints=web
398
- - traefik.http.routers.myapp-http.middlewares=redirect-to-https
399
- - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
400
- - traefik.http.services.myapp.loadbalancer.server.port=3000
401
-
402
- networks:
403
- traefik:
404
- external: true
405
- internal:
406
- ```
407
-
408
- ---
409
-
410
- ## Pre-Deploy Compose Checklist
411
-
412
- - [ ] All image versions replaced with `${APP_VERSION}` (no `v` prefix before the variable)
413
- - [ ] All database passwords replaced with `${DB_PASSWORD}`
414
- - [ ] No hardcoded secrets or credentials in the file
415
- - [ ] Main service is on both `traefik` and `internal` networks
416
- - [ ] Database/cache services are on `internal` network only
417
- - [ ] Network definitions at the bottom: `traefik` is `external: true`, `internal` has no extra config
418
- - [ ] Traefik router name is unique (format: `{appname}{role}`)
419
- - [ ] Domain rule uses exact syntax with `${BASE_DOMAIN}` and `${CUSTOM_DOMAIN:+ ...}` expansion
420
- - [ ] `loadbalancer.server.port` matches the actual port the container listens on
421
- - [ ] TLS labels present: `tls=true` + `tls.certresolver=letsencrypt`
422
- - [ ] No `\` backslashes before `$` in URLs
423
- - [ ] No comments (`#`) in the final file
424
- - [ ] No `deploy`, `resources`, `limits`, or `reservations` sections
425
- - [ ] All volumes declared in the `volumes:` section at the bottom
@@ -1,179 +0,0 @@
1
- # Ilumin Recommended Stack
2
-
3
- When the user hasn't specified which infrastructure services to use, follow this recommended stack. These services are optimized for Ilumin Cloud — they are lightweight, self-hosted, and integrate well with each other via the `internal` Docker network.
4
-
5
- ---
6
-
7
- ## How Internal Communication Works
8
-
9
- All services in the same `docker-compose.yml` (or on the same `internal` Docker network) can communicate using the **service name as the hostname**.
10
-
11
- ```
12
- App Container → [internal network] → postgres:5432
13
- App Container → [internal network] → redis:6379
14
- NocoDB → [internal network] → postgres:5432
15
- ```
16
-
17
- > The key rule: **both the app and the service it connects to must share the same network** (always `internal`). The public internet never touches these internal services — only the main app container is exposed via Traefik.
18
-
19
- ---
20
-
21
- ## 1. 🗄️ Database: NocoDB + PostgreSQL
22
-
23
- **NocoDB** is the recommended database layer for Ilumin apps. It is a lightweight, self-hosted Airtable alternative that sits on top of PostgreSQL and exposes a full REST + GraphQL API, a visual UI, and programmatic table creation via API — no raw SQL required for most operations.
24
-
25
- ### Why NocoDB?
26
- - Full REST API to read/write/create tables from any app or AI agent
27
- - Visual interface for non-developers to manage data
28
- - Integrates with webhooks, automations, and third-party tools
29
- - JWT-based API authentication via `NC_AUTH_JWT_SECRET`
30
-
31
- ### Connecting Your App to NocoDB
32
-
33
- NocoDB exposes an HTTP API. Your app does **not** connect to NocoDB as a database directly — it makes API calls to NocoDB's REST endpoint.
34
-
35
- If your app is in the **same compose** or **same `internal` network**, use the internal hostname:
36
-
37
- ```bash
38
- # Internal (same internal network) — preferred
39
- NOCODB_URL=http://nocodb:8080
40
-
41
- # External (different server or no shared network)
42
- NOCODB_URL=https://your-nocodb-domain.com
43
- ```
44
-
45
- ```python
46
- # Python example — connecting via internal network
47
- import httpx
48
-
49
- NOCODB_URL = os.getenv("NOCODB_URL", "http://nocodb:8080")
50
- NOCODB_API_KEY = os.getenv("NOCODB_API_KEY") # = NC_AUTH_JWT_SECRET value
51
-
52
- headers = {
53
- "xc-auth": NOCODB_API_KEY,
54
- "Content-Type": "application/json"
55
- }
56
-
57
- # List records from a table
58
- response = httpx.get(f"{NOCODB_URL}/api/v1/db/data/noco/{{TABLE_ID}}/{{VIEW_ID}}", headers=headers)
59
- ```
60
-
61
- ### If NocoDB Is Already Installed
62
-
63
- Before installing a new NocoDB, check if one is already running on the server:
64
-
65
- 1. Run `list_servers` to see installed apps.
66
- 2. If NocoDB is already there, retrieve its `NC_AUTH_JWT_SECRET` env var — this is the API key your app uses to authenticate.
67
- 3. Use the internal URL `http://nocodb:8080` if your app is in the same compose, or the public domain if separate.
68
-
69
- ---
70
-
71
- ## 2. ⚡ Cache & Sessions: Redis + RedisInsight
72
-
73
- **Redis** is the recommended solution for caching, session storage, and rate limiting. **RedisInsight** is the official Redis visual UI — install them together for observability.
74
-
75
- ### Connecting Your App to Redis
76
-
77
- ```bash
78
- # Internal connection string (same internal network)
79
- REDIS_URL=redis://redis:6379
80
- ```
81
-
82
- ```typescript
83
- // Node.js / TypeScript — ioredis
84
- import Redis from 'ioredis';
85
-
86
- const redis = new Redis(process.env.REDIS_URL || 'redis://redis:6379');
87
-
88
- // Cache example
89
- await redis.set('key', 'value', 'EX', 3600); // expires in 1 hour
90
- const value = await redis.get('key');
91
- ```
92
-
93
- ```python
94
- # Python — redis-py
95
- import redis
96
- import os
97
-
98
- r = redis.from_url(os.getenv("REDIS_URL", "redis://redis:6379"))
99
-
100
- r.set("key", "value", ex=3600)
101
- value = r.get("key")
102
- ```
103
-
104
- > **Network rule:** Redis is on `internal` only — it is **never** exposed publicly. RedisInsight (the UI) is on `traefik + internal` so it can be accessed via browser. Your app connects to `redis:6379` on the `internal` network.
105
-
106
- ---
107
-
108
- ## 3. 🔄 Job Queues: BullMQ (uses Redis)
109
-
110
- **BullMQ** is the recommended job queue solution. It uses Redis as its backend — so if Redis is already installed (from item 2), BullMQ is ready to use with no extra infrastructure.
111
-
112
- **BullMQ is a library, not a separate service.** It runs inside your application code.
113
-
114
- ### When to use BullMQ
115
- - Sending emails asynchronously
116
- - Processing uploaded files or images
117
- - Scheduling recurring tasks (cron-like)
118
- - Handling webhooks with retry logic
119
- - Any long-running task that shouldn't block an HTTP response
120
-
121
- ### BullMQ Integration (Node.js / TypeScript)
122
-
123
- ```typescript
124
- import { Queue, Worker } from 'bullmq';
125
- import IORedis from 'ioredis';
126
-
127
- const connection = new IORedis(process.env.REDIS_URL || 'redis://redis:6379', {
128
- maxRetriesPerRequest: null, // required for BullMQ
129
- });
130
-
131
- // --- Producer (add jobs to the queue) ---
132
- const emailQueue = new Queue('emails', { connection });
133
-
134
- await emailQueue.add('send-welcome', {
135
- to: 'user@example.com',
136
- subject: 'Welcome!',
137
- });
138
-
139
- // --- Worker (process jobs from the queue) ---
140
- const worker = new Worker('emails', async (job) => {
141
- const { to, subject } = job.data;
142
- await sendEmail(to, subject); // your email logic
143
- }, { connection });
144
-
145
- worker.on('completed', (job) => console.log(`Job ${job.id} completed`));
146
- worker.on('failed', (job, err) => console.error(`Job ${job?.id} failed:`, err));
147
- ```
148
-
149
- ### BullMQ Integration (Python — with rq or celery as alternatives)
150
-
151
- > BullMQ is Node.js-native. For Python apps, use **Celery + Redis** or **rq + Redis** instead — same Redis instance, same connection string.
152
-
153
- ```python
154
- # Python — Celery with Redis broker
155
- from celery import Celery
156
-
157
- app = Celery('tasks', broker=os.getenv('REDIS_URL', 'redis://redis:6379/0'))
158
-
159
- @app.task
160
- def send_email(to: str, subject: str):
161
- # your email logic
162
- pass
163
-
164
- # Calling the task (async)
165
- send_email.delay('user@example.com', 'Welcome!')
166
- ```
167
-
168
- ---
169
-
170
- ## Decision Guide
171
-
172
- | Need | Recommended Solution |
173
- |---|---|
174
- | Store and query structured data | NocoDB + PostgreSQL |
175
- | Cache API responses, store sessions | Redis |
176
- | Background jobs, async tasks, retries | BullMQ (Node.js) / Celery (Python) — both use Redis |
177
- | Visual database management | NocoDB UI (built-in) |
178
- | Visual Redis inspection | RedisInsight |
179
- | All of the above | Full stack compose above |
@@ -1,355 +0,0 @@
1
- ---
2
- name: ilumin-security-reviewer
3
- description: Use this skill before deploying any application to production. It covers mandatory security practices including authentication, frontend/backend separation, input validation, secrets management, dependency hygiene, and audit logging. Apply these checks on every new feature or deployment.
4
- ---
5
-
6
- # Application Security Reviewer
7
-
8
- This skill defines the mandatory security standards that must be verified **before deploying any application to production**. Apply each section as a checklist. Missing any item can expose user data, allow unauthorized access, or compromise the entire platform.
9
-
10
- ---
11
-
12
- ## 1. Authentication & Session Management
13
-
14
- ### Bearer Token / JWT
15
- All protected API routes **must** require an `Authorization: Bearer <token>` header. Unauthenticated requests must return `401 Unauthorized` immediately — no partial data should ever be returned.
16
-
17
- ```python
18
- # Correct — FastAPI example
19
- from fastapi import Depends, HTTPException, status
20
- from fastapi.security import HTTPBearer
21
-
22
- security = HTTPBearer()
23
-
24
- @app.get("/api/protected")
25
- def protected_route(token = Depends(security)):
26
- user = verify_jwt(token.credentials)
27
- if not user:
28
- raise HTTPException(status_code=401, detail="Unauthorized")
29
- ```
30
-
31
- ```typescript
32
- // Correct — Express.js example
33
- app.use('/api', (req, res, next) => {
34
- const token = req.headers.authorization?.split(' ')[1];
35
- if (!token || !verifyJWT(token)) return res.status(401).json({ error: 'Unauthorized' });
36
- next();
37
- });
38
- ```
39
-
40
- ### Password Storage
41
- - **NEVER** store passwords in plain text or use MD5/SHA-1.
42
- - Use **Argon2id** (preferred) or **BCrypt** with a minimum work factor of 10.
43
- - Or use PostgreSQL's `pgCrypto` extension:
44
- ```sql
45
- -- Store: crypt('user_password', gen_salt('bf', 10))
46
- -- Verify: SELECT (password_hash = crypt('attempt', password_hash)) AS match FROM users WHERE id = $1;
47
- ```
48
-
49
- ### Cookie Security Flags
50
- All session cookies **must** include:
51
- - `HttpOnly` — prevents `document.cookie` access (mitigates XSS).
52
- - `Secure` — transmits cookies over HTTPS only.
53
- - `SameSite=Lax` or `Strict` — protects against CSRF attacks.
54
-
55
- ---
56
-
57
- ## 2. Frontend/Backend Separation
58
-
59
- **The frontend must NEVER make direct calls to third-party APIs with sensitive credentials.** Any API key, auth header, or secret that's placed in frontend code is exposed to every user via browser dev tools.
60
-
61
- ### The Correct Pattern
62
-
63
- ```
64
- User Browser → Frontend (no secrets) → Your Backend API → Third-Party API (with secrets)
65
- ```
66
-
67
- **Why this matters:** If your app calls the Evolution API, an AI API, or any service with an API key directly from the frontend, the user can open the Network tab in DevTools and steal that key.
68
-
69
- ```typescript
70
- // WRONG — API key exposed in frontend
71
- const response = await fetch('https://api.openai.com/v1/chat', {
72
- headers: { 'Authorization': `Bearer ${OPENAI_KEY}` } // USER CAN SEE THIS
73
- });
74
-
75
- // CORRECT — Frontend calls your own backend
76
- const response = await fetch('/api/chat', {
77
- headers: { 'Authorization': `Bearer ${userJwtToken}` }
78
- });
79
- // Your backend then calls OpenAI with the secret key stored in env vars
80
- ```
81
-
82
- ### What Belongs Where
83
-
84
- | Layer | Allowed | Forbidden |
85
- |---|---|---|
86
- | **Frontend** | User JWT tokens, public config, UI logic | API keys, DB credentials, internal service URLs |
87
- | **Backend** | All secrets, third-party API calls, DB queries | Returning raw sensitive data without filtering |
88
-
89
- ---
90
-
91
- ## 3. GOLDEN RULE — Input Validation & Sanitization
92
-
93
- > **Never trust user input. Always validate on the backend.**
94
-
95
- Frontend validation is for UX only. A malicious user can bypass it entirely. Every piece of data entering your backend must be validated for **type, size, and format** before use.
96
-
97
- ### SQL Injection Prevention
98
- Always use **Prepared Statements** or ORM parameterized queries. Never interpolate user input into SQL strings.
99
-
100
- ```python
101
- # WRONG — SQL Injection vulnerability
102
- query = f"SELECT * FROM users WHERE email = '{user_email}'"
103
-
104
- # CORRECT — Parameterized query
105
- query = "SELECT * FROM users WHERE email = $1"
106
- result = await db.fetch(query, user_email)
107
- ```
108
-
109
- ### XSS Prevention
110
- - Use template engines with **auto-escaping** enabled (React does this by default for JSX).
111
- - Never use `dangerouslySetInnerHTML` with unescaped user content.
112
- - Sanitize HTML input with a library like `DOMPurify` if rich text is required.
113
-
114
- ### Input Schema Validation (Backend)
115
- Validate every request body with a schema library before processing:
116
-
117
- ```python
118
- # FastAPI + Pydantic — automatic type and size validation
119
- from pydantic import BaseModel, EmailStr, constr
120
-
121
- class CreateUserRequest(BaseModel):
122
- name: constr(min_length=2, max_length=100)
123
- email: EmailStr
124
- password: constr(min_length=8, max_length=128)
125
- ```
126
-
127
- ```typescript
128
- // Node.js + Zod
129
- import { z } from 'zod';
130
-
131
- const schema = z.object({
132
- name: z.string().min(2).max(100),
133
- email: z.string().email(),
134
- password: z.string().min(8).max(128),
135
- });
136
-
137
- const data = schema.parse(req.body); // throws if invalid
138
- ```
139
-
140
- ### IDOR (Insecure Direct Object Reference) Prevention
141
- Never trust the resource ID from the request alone. Always verify ownership:
142
-
143
- ```sql
144
- -- WRONG: assumes the user owns order 500
145
- SELECT * FROM orders WHERE id = $1;
146
-
147
- -- CORRECT: validates ownership using the authenticated user's ID from the session
148
- SELECT * FROM orders WHERE id = $1 AND customer_id = $2;
149
- ```
150
-
151
- ---
152
-
153
- ## 4. Secrets & Environment Variables
154
-
155
- ### Zero Hardcoding Rule
156
- No secret must **ever** appear in source code or be committed to Git.
157
-
158
- ```bash
159
- # WRONG — hardcoded secret in code
160
- DATABASE_URL = "postgresql://user:SuperSecret123@db:5432/mydb"
161
- API_KEY = "sk-abc123..."
162
-
163
- # CORRECT — read from environment
164
- import os
165
- DATABASE_URL = os.getenv("DATABASE_URL")
166
- API_KEY = os.getenv("API_KEY")
167
- ```
168
-
169
- ### .env File Rules
170
- - **Commit `.env.example`** (with placeholder values) — never the actual `.env`.
171
- - Add `.env` to `.gitignore` on day one of the project.
172
- - On Ilumin Cloud, configure secrets as environment variables in the compose file using the `${VAR_NAME}` syntax.
173
-
174
- ```yaml
175
- # Correct use in docker-compose.yml for Ilumin Cloud
176
- environment:
177
- DATABASE_URL: ${DATABASE_URL}
178
- SECRET_KEY: ${SECRET_KEY}
179
- API_KEY: ${API_KEY}
180
- ```
181
-
182
- ### Git Leak Prevention
183
- Before any `git push`, verify no secrets were accidentally staged:
184
- - Use `git diff --staged` to review what's being committed.
185
- - Optionally install `gitleaks` or `trufflehog` as a pre-commit hook.
186
-
187
- ---
188
-
189
- ## 5. Dependency Hygiene
190
-
191
- Outdated dependencies are a common attack vector. Old versions frequently contain known CVEs (Common Vulnerabilities and Exposures).
192
-
193
- ### Regular Audit Commands
194
- Run these before every production deployment:
195
-
196
- ```bash
197
- # Node.js / npm
198
- npm audit
199
- npm audit fix
200
-
201
- # Python
202
- pip-audit
203
- # or
204
- safety check -r requirements.txt
205
-
206
- # Check for outdated packages
207
- npm outdated
208
- pip list --outdated
209
- ```
210
-
211
- ### Lock Files
212
- Always commit your lock file to guarantee reproducible and secure builds:
213
- - `package-lock.json` or `yarn.lock` for Node.js
214
- - `poetry.lock` or `requirements.txt` with pinned versions for Python
215
-
216
- ```txt
217
- # Pin exact versions in requirements.txt
218
- fastapi==0.111.0
219
- pydantic==2.7.1
220
- httpx==0.27.0
221
-
222
- # Avoid unpinned versions in production
223
- fastapi>=0.100
224
- ```
225
-
226
- ---
227
-
228
- ## 6. Audit Logging
229
-
230
- Log security-critical events to enable forensic analysis in case of an incident:
231
-
232
- | Event | Must be logged |
233
- |---|---|
234
- | Failed login attempts | Yes |
235
- | Successful logins | Yes |
236
- | Permission/role changes | Yes |
237
- | Access to sensitive data (PII) | Yes |
238
- | Validation errors that look like attacks | Yes |
239
- | Password resets | Yes |
240
-
241
- > **WARNING:** Never log passwords, full credit card numbers, or complete authentication tokens. Log only partial data (e.g., last 4 digits, masked email).
242
-
243
- ```python
244
- # Safe logging example
245
- import logging
246
-
247
- logger = logging.getLogger("security")
248
-
249
- def on_failed_login(email: str, ip: str):
250
- masked_email = email[:3] + "***@" + email.split("@")[-1]
251
- logger.warning(f"Failed login attempt | email={masked_email} | ip={ip}")
252
- ```
253
-
254
- ---
255
-
256
- ## 7. Access Control (RBAC)
257
-
258
- Apply the **Principle of Least Privilege**: users should only access what they need for their role.
259
-
260
- - Define roles explicitly: `admin`, `editor`, `viewer`, etc.
261
- - Enforce role checks **on the backend**, never just on frontend route guards.
262
- - For SaaS/multi-tenant apps: always scope queries to the authenticated user's `tenant_id` or `organization_id` — never return cross-tenant data.
263
-
264
- ```python
265
- # Role check middleware example
266
- def require_role(required_role: str):
267
- def decorator(user = Depends(get_current_user)):
268
- if user.role != required_role:
269
- raise HTTPException(status_code=403, detail="Forbidden")
270
- return user
271
- return decorator
272
-
273
- @app.delete("/api/users/{id}", dependencies=[Depends(require_role("admin"))])
274
- def delete_user(id: int): ...
275
- ```
276
-
277
- ---
278
-
279
- ## 8. Rate Limiting & Abuse Prevention (Frontend + Backend)
280
-
281
- To prevent users from creating automations (like malicious scripts or spamming clicks) that overwhelm the database—such as creating 500 rows per second and overloading the CPU and storage—you **MUST** implement rate limiting at both layers:
282
-
283
- ### Frontend Defenses
284
- While frontend protections can be bypassed by advanced users, they prevent accidental spam and simple UI-based automations:
285
- - **Disable Buttons on Submit:** Always disable the submit button and show a loading state while a request is in flight.
286
- - **Debounce / Throttle:** Use debouncing for rapid events (like search inputs) to prevent firing an API call on every keystroke.
287
- - **Cooldowns:** For specific actions (e.g., "Send SMS", "Generate Image"), implement a visual cooldown timer.
288
-
289
- ```tsx
290
- // Correct — Disable button while loading to prevent double-clicks / spam
291
- const [isLoading, setIsLoading] = useState(false);
292
-
293
- const handleSubmit = async () => {
294
- if (isLoading) return;
295
- setIsLoading(true);
296
- try {
297
- await api.post('/data', payload);
298
- } finally {
299
- setIsLoading(false);
300
- }
301
- };
302
-
303
- return (
304
- <button disabled={isLoading} onClick={handleSubmit}>
305
- {isLoading ? 'Processing...' : 'Submit'}
306
- </button>
307
- );
308
- ```
309
-
310
- ### Backend Rate Limiting (The Real Shield)
311
- Since frontend limits can be bypassed via direct API calls (e.g., cURL, Postman), the backend **MUST** enforce the ultimate rate limit per IP or User ID.
312
- - Apply strict limits to all `POST`, `PUT`, `PATCH`, and `DELETE` endpoints.
313
- - E.g., Limit data creation (posts, comments) to a reasonable amount per minute to protect database IO and CPU.
314
-
315
- ---
316
-
317
- ## Pre-Deployment Security Checklist
318
-
319
- Run through every item before a production deploy:
320
-
321
- ### Authentication & Authorization
322
- - [ ] All protected routes require a valid Bearer token
323
- - [ ] JWT expiration is set (recommended: ≤ 24h for access tokens)
324
- - [ ] Passwords are hashed with BCrypt/Argon2 or pgCrypto — never stored plain
325
- - [ ] Session cookies have `HttpOnly`, `Secure`, and `SameSite` flags set
326
-
327
- ### Frontend/Backend Separation
328
- - [ ] No API keys or secrets exist in frontend code
329
- - [ ] All sensitive third-party calls are proxied through the backend
330
- - [ ] Frontend only stores the user's own JWT — nothing else sensitive
331
-
332
- ### Input Validation (Golden Rule)
333
- - [ ] All request bodies are validated with a schema (Pydantic, Zod, Joi, etc.)
334
- - [ ] All database queries use parameterized statements / ORM methods
335
- - [ ] Ownership is verified for every resource access (IDOR prevention)
336
- - [ ] All user-generated content rendered in HTML is escaped
337
-
338
- ### Rate Limiting & Abuse Prevention
339
- - [ ] Frontend UI disables buttons/inputs while requests are pending
340
- - [ ] Backend strictly implements rate limits on mutation endpoints (`POST`, `PUT`, `DELETE`) to avoid database stuffing
341
-
342
- ### Secrets & Environment
343
- - [ ] No hardcoded secrets in source code
344
- - [ ] `.env` is in `.gitignore` and not committed
345
- - [ ] `.env.example` exists with placeholder values for team reference
346
- - [ ] All production secrets are configured as environment variables
347
-
348
- ### Dependencies
349
- - [ ] `npm audit` / `pip-audit` ran with no critical vulnerabilities
350
- - [ ] Lock file is committed and up to date
351
- - [ ] No dependency is significantly outdated with known CVEs
352
-
353
- ### Logging
354
- - [ ] Failed login events are being logged
355
- - [ ] No sensitive data (passwords, tokens, card numbers) appears in logs