enigma-cli 1.32.3 → 1.32.4
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/assets/commands/gate.md +40 -7
- package/assets/memory/AGENTS.md +3 -2
- package/assets/memory/CLAUDE.md +3 -2
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/SKILL.md +24 -0
- package/assets/skills/frontend-policy/skill.json +3 -3
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/SKILL.md +37 -1
- package/assets/skills/security-policy/skill.json +3 -3
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/dist/guardrails.js +265 -4
- package/package.json +1 -1
package/assets/commands/gate.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Validate code changes through the enigma gate pipeline - automated review, tests, lint, docs, push, PR, and CI - before they reach the configured push target. Use when the user asks to run the gate, gate/ship/validate their changes, push safely, do a task and then validate it, or invokes /gate.
|
|
3
|
-
argument-hint: [task] | (bare to gate already-committed work) | "skip the lint step"-style requests
|
|
2
|
+
description: Validate code changes through the enigma gate pipeline - automated review, tests, lint, docs, push, PR, and CI - before they reach the configured push target. Use when the user asks to run the gate, gate/ship/validate their changes, push safely, do a task and then validate it, or invokes /gate. Also the way to turn the gate on or off (/gate on, /gate off, /gate status) when the user asks to enable, disable, or stop it running automatically.
|
|
3
|
+
argument-hint: [task] | (bare to gate already-committed work) | on | off | status | "skip the lint step"-style requests
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /gate
|
|
@@ -18,6 +18,33 @@ for something specific, translate it into the matching `axi run` flags yourself
|
|
|
18
18
|
for example "skip the lint step" becomes `--skip=lint`. Run
|
|
19
19
|
`enigma gate axi run --help` to see the flags.
|
|
20
20
|
|
|
21
|
+
## Turning the gate on and off
|
|
22
|
+
|
|
23
|
+
`/gate on`, `/gate off` and `/gate status` are settings, not runs - handle them
|
|
24
|
+
first and stop there, without touching the pipeline.
|
|
25
|
+
|
|
26
|
+
| Invocation | Command to run |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `/gate status` | `enigma config gate` (prints the value in force here) |
|
|
29
|
+
| `/gate off` | `enigma config gate off -l` |
|
|
30
|
+
| `/gate on` | `enigma config gate on -l` |
|
|
31
|
+
| `/gate off --global` (or "everywhere", "in every project") | `enigma config gate off -g` |
|
|
32
|
+
| `/gate on --global` | `enigma config gate on -g` |
|
|
33
|
+
|
|
34
|
+
Scope matters, so be explicit about it. Plain `/gate off` switches the gate off
|
|
35
|
+
for **this project only** by writing `gate: false` into its `.enigma.json` -
|
|
36
|
+
narrow and reversible, and it leaves every other repo alone. Only go global when
|
|
37
|
+
the user actually says so. Report which of the two you applied.
|
|
38
|
+
|
|
39
|
+
Toggling rewrites the agent memory file, which is read at startup: tell the user
|
|
40
|
+
the change takes effect in their **next** session. In this one, keep honouring
|
|
41
|
+
what they just asked for - if they turned the gate off, do not drive it again.
|
|
42
|
+
|
|
43
|
+
One asymmetry worth stating when it applies: a **global** off also removes this
|
|
44
|
+
command from the agent, so tell the user that turning it back on is
|
|
45
|
+
`enigma config gate on -g` from a terminal. A project-scoped off leaves `/gate`
|
|
46
|
+
in place, so `/gate on` works there.
|
|
47
|
+
|
|
21
48
|
## Two ways to invoke
|
|
22
49
|
|
|
23
50
|
- **Validate-only** - bare `/gate` (optionally with flag-style requests like
|
|
@@ -27,15 +54,19 @@ for example "skip the lint step" becomes `--skip=lint`. Run
|
|
|
27
54
|
carry out the task yourself, then validate:
|
|
28
55
|
1. **Check scope.** Inspect `git status` before changing or committing anything.
|
|
29
56
|
Preserve unrelated uncommitted changes; commit only what belongs to the task.
|
|
30
|
-
2. **Do the work**, then **commit it
|
|
31
|
-
the
|
|
32
|
-
|
|
57
|
+
2. **Do the work**, then **commit it**. The gate validates committed history on
|
|
58
|
+
whatever branch the user is on - never switch or create a branch just to run
|
|
59
|
+
it.
|
|
33
60
|
3. **Then validate**, passing the user's task as your `--intent` (the goal in
|
|
34
61
|
their words), enriched with the decisions and tradeoffs you made.
|
|
35
62
|
|
|
36
63
|
## Before you start
|
|
37
64
|
|
|
38
|
-
- The work must be **committed
|
|
65
|
+
- The work must be **committed**. Any branch qualifies, the default branch
|
|
66
|
+
included - there the pipeline opens no PR and its push lands directly on that
|
|
67
|
+
branch, which is the intended behavior. The only exception is a branch the user
|
|
68
|
+
listed in `gate-protected-branches`: `axi run` refuses it, and the answer is to
|
|
69
|
+
tell the user, not to move their work to another branch.
|
|
39
70
|
- The repository must be initialized with `enigma gate init`.
|
|
40
71
|
|
|
41
72
|
If any precondition fails, `axi run` returns an `error:` with the exact fix -
|
|
@@ -92,7 +123,9 @@ uses `--intent` to tell a deliberate choice from a mistake.
|
|
|
92
123
|
driving. Tell the user the PR is ready and ask them to review and merge it
|
|
93
124
|
(link in the `help` line). The gate keeps monitoring the PR in the
|
|
94
125
|
background; do not poll for the merge.
|
|
95
|
-
- `passed` - cleared the gate and the PR was merged or closed.
|
|
126
|
+
- `passed` - cleared the gate and the PR was merged or closed. On the default
|
|
127
|
+
branch this is the normal ending: no PR is opened and CI monitoring is
|
|
128
|
+
skipped, so the run finishes here once the push lands.
|
|
96
129
|
- `failed` / `cancelled` - read the output, fix what it points at, commit on
|
|
97
130
|
the same branch, and drive again (`axi run` for a fresh run, or
|
|
98
131
|
`enigma gate rerun`). Do not leave the user at a failed outcome without
|
package/assets/memory/AGENTS.md
CHANGED
|
@@ -83,8 +83,9 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
83
83
|
<!-- enigma:gate:start -->
|
|
84
84
|
### AI Quality Gate (Automatic)
|
|
85
85
|
|
|
86
|
-
- The AI quality gate is active. When you finish a code task
|
|
87
|
-
-
|
|
86
|
+
- The AI quality gate is active. When you finish a code task and the changes are committed, drive the gate yourself before reporting the work done - on WHATEVER branch the work is on, the default branch included. The user should not have to ask for it or run any setup command.
|
|
87
|
+
- Skip automatic driving only when: the user told you to skip it, the repo's `.enigma.json` sets `gate: false`, there is nothing committed to validate, or `axi run` refuses the branch as protected (`gate-protected-branches`) - then say so and leave the work unvalidated rather than switching branches on your own. To stop it for good the user runs `/gate off` (this project) or `enigma config gate off -g` (everywhere); offer that instead of quietly not running it.
|
|
88
|
+
- On the default branch the pipeline opens no PR and its push lands directly on that branch. That is the intended behavior there; report it as the outcome instead of waiting for a PR link.
|
|
88
89
|
- If the repo is not initialized yet (`enigma gate axi` says so), run `enigma gate init` once yourself, then proceed. If `enigma gate` itself misbehaves, `enigma gate doctor` reports why.
|
|
89
90
|
- Drive it through the `/gate` workflow (`enigma gate axi run --intent "<what the user set out to accomplish>"`): authorize `auto-fix` and `no-op` findings on your own judgment, but STOP and escalate every `ask-user` finding verbatim. Do NOT pass `--yes` and do NOT merge the PR yourself - on `checks-passed`, leave the PR ready and ask the user to review and merge it.
|
|
90
91
|
- While a run is active never edit code to fix a finding; the pipeline owns the fixes (respond with `--action fix`). The full command reference lives in the gate skill / `/gate` command.
|
package/assets/memory/CLAUDE.md
CHANGED
|
@@ -83,8 +83,9 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
83
83
|
<!-- enigma:gate:start -->
|
|
84
84
|
### AI Quality Gate (Automatic)
|
|
85
85
|
|
|
86
|
-
- The AI quality gate is active. When you finish a code task
|
|
87
|
-
-
|
|
86
|
+
- The AI quality gate is active. When you finish a code task and the changes are committed, drive the gate yourself before reporting the work done - on WHATEVER branch the work is on, the default branch included. The user should not have to ask for it or run any setup command.
|
|
87
|
+
- Skip automatic driving only when: the user told you to skip it, the repo's `.enigma.json` sets `gate: false`, there is nothing committed to validate, or `axi run` refuses the branch as protected (`gate-protected-branches`) - then say so and leave the work unvalidated rather than switching branches on your own. To stop it for good the user runs `/gate off` (this project) or `enigma config gate off -g` (everywhere); offer that instead of quietly not running it.
|
|
88
|
+
- On the default branch the pipeline opens no PR and its push lands directly on that branch. That is the intended behavior there; report it as the outcome instead of waiting for a PR link.
|
|
88
89
|
- If the repo is not initialized yet (`enigma gate axi` says so), run `enigma gate init` once yourself, then proceed. If `enigma gate` itself misbehaves, `enigma gate doctor` reports why.
|
|
89
90
|
- Drive it through the `/gate` workflow (`enigma gate axi run --intent "<what the user set out to accomplish>"`): authorize `auto-fix` and `no-op` findings on your own judgment, but STOP and escalate every `ask-user` finding verbatim. Do NOT pass `--yes` and do NOT merge the PR yourself - on `checks-passed`, leave the PR ready and ask the user to review and merge it.
|
|
90
91
|
- While a run is active never edit code to fix a finding; the pipeline owns the fixes (respond with `--action fix`). The full command reference lives in the gate skill / `/gate` command.
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "On-demand over-engineering review - diff review, whole-repo audit, and enigma: debt-marker ledger (tags delete/stdlib/native/yagni/shrink, line/dep scoring); lists cuts, applies nothing.",
|
|
6
6
|
"updated": "2026-06-16T11:24:30+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "f742a2be3f328b9ea1ff9a35a449177c2cbec35ad16e46f7054b7a873a2ab017"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Backend/API architecture: controller-service-repository layering, API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
|
|
6
6
|
"updated": "2026-07-23T04:41:39+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "d091e8708888d23ee14cdd6c0e71f386861f2252d95360895a31c73db090a689"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Ciphera code style conventions (formatting, naming, imports incl. namespace imports for wide module surfaces, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
6
|
"updated": "2026-07-30T15:48:04+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "05dc812da459071110d96ee41c5201b1ee230471d635352415e10ef8b220c267"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
|
|
6
6
|
"updated": "2026-06-01T00:45:28+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
|
|
6
6
|
"updated": "2026-07-30T19:29:19+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "6a881d8589926fa7f48058314fd26d7042fd2ac82f1c87a6c11ffb54d1fda22b"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Senior database architecture policy: query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
|
|
6
6
|
"updated": "2026-06-03T14:19:50+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "2883bcecb3202683ae6f81b073c3d6a9cec9c55029e011bdd06ba7ac3537297e"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
|
|
6
6
|
"updated": "2026-06-01T00:45:28+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "14b0064c8b33a0dc85e51464b05005cf5801c756b1101789a6924b9548420f6b"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
|
|
6
6
|
"updated": "2026-06-01T00:45:28+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Transactional email: React Email templates instead of hand-written HTML tables, server-side rendering, one send module behind the provider SDK, plain-text alternatives, idempotent background sending, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).",
|
|
6
6
|
"updated": "2026-07-30T19:29:19+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "c9724fdbcdbeab99573be3fd44d4cdd97c2a394d99f3c4395f118f17356b00ed"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one.",
|
|
6
6
|
"updated": "2026-07-29T01:18:36+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
|
|
9
9
|
}
|
|
@@ -134,6 +134,16 @@ Navigation and any other persistent chrome must stay where the user left it whil
|
|
|
134
134
|
- The same rule covers a sticky header, toolbar, filter rail or side panel: pinned, bounded, and independently scrollable. Keep pinned chrome shallow - it eats vertical space on small screens, so collapse it into a drawer or a top bar there rather than pinning it over half the viewport.
|
|
135
135
|
- Verify by scrolling to the BOTTOM of a long page and confirming the sidebar is still on screen with its last entry reachable. Mechanically: the sidebar's `getBoundingClientRect().top` stays at its offset as the page scrolls, and `scrollHeight <= clientHeight` holds for it, or it can scroll itself.
|
|
136
136
|
|
|
137
|
+
### On a phone the sidebar takes the whole screen
|
|
138
|
+
|
|
139
|
+
A sidebar keeps its desktop width only while there is a desktop to put it in. On a phone it becomes a full-screen surface, unless the design or the user says otherwise. Apply this by default to any off-canvas chrome: the nav sidebar, a filter rail, a details drawer, a settings panel.
|
|
140
|
+
|
|
141
|
+
- Full width AND full height: `w-full` (`100%`, `100vw`) with the desktop width added at a breakpoint (`w-full md:w-80`) or capped by `max-w-*`. Prefer `100dvh` over `100vh` for the height so the mobile browser's collapsing toolbar does not cut the panel off.
|
|
142
|
+
- A fixed width on a phone is the defect either way it lands: 320px on a 360px screen leaves a useless sliver of dead content, and anything wider than the viewport is simply cut off.
|
|
143
|
+
- The stacked variant counts as full width: when the layout collapses to one column and the sidebar returns to normal flow, it already spans the screen. Nothing more to do there.
|
|
144
|
+
- Being full screen makes it modal, so treat it as one: a visible close control, dismissal by backdrop tap and Escape, focus moved into the panel and trapped while it is open, focus restored to the trigger on close, and the page behind it locked from scrolling.
|
|
145
|
+
- The exceptions are real but explicit: a design that deliberately keeps a peek of the content behind, and a panel that is hidden on phones entirely because a different component serves that size. Say so in the code rather than leaving it to be read as an oversight.
|
|
146
|
+
|
|
137
147
|
---
|
|
138
148
|
|
|
139
149
|
## Links In Copy Are Links
|
|
@@ -222,6 +232,20 @@ When the user edits a value that must be unique within a set the client already
|
|
|
222
232
|
|
|
223
233
|
---
|
|
224
234
|
|
|
235
|
+
## Sign-In, Sign-Up and Recovery Screens
|
|
236
|
+
|
|
237
|
+
Auth is the first screen a user meets and the one most often shipped half-built. Treat the four screens as one flow: sign in, sign up, forgot password, set a new password. The server-side rules (token lifetime, rate limits, what an answer may reveal) are owned by security-policy; what follows is the UI half.
|
|
238
|
+
|
|
239
|
+
- Every sign-in form with a password field carries a visible "Forgot your password?" link next to that field, leading to a real reset flow. Building the login screen without it is shipping a dead end.
|
|
240
|
+
- The reset request screen confirms in the same words whether or not the address is registered ("If that address has an account, we have sent a link"). Never render "no account with that email" - the screen would be an account-existence oracle.
|
|
241
|
+
- The new-password screen validates in real time against the same schema the server uses, uses the shared Input (which brings the show/hide toggle), and compares the confirmation field as the user types. Keep Submit disabled until both are valid, with the reason visible.
|
|
242
|
+
- After sign-up the user lands inside the app, already signed in. If the account still needs email verification, say so in the app with a way to resend, and block only the actions that need it.
|
|
243
|
+
- Surface throttling honestly. On a `429`, show how long the wait is (from `Retry-After`), keep the button disabled with a countdown, and never swallow the response into a generic "something went wrong".
|
|
244
|
+
- A one-time-code field is one input with `autocomplete="one-time-code"`, `inputmode="numeric"`, paste of the whole code, and no clearing of what the user typed on a wrong attempt. Say how many attempts are left only if the server chose to reveal it.
|
|
245
|
+
- Never keep a password, token, or code in `localStorage`, a query string, or an analytics payload. A reset token in the URL stays out of logs and out of any third-party script on the page.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
225
249
|
## Client-Side Caching (Reduce Server Load)
|
|
226
250
|
|
|
227
251
|
Cache on the client to avoid redundant server round-trips and to keep the app usable under rate limits. The goal is to reach the backend (and therefore Redis/DB) as rarely as correctness allows.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.20.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Frontend architecture: reusable components, abstraction thresholds, state management, no-op detection (skip any operation whose result equals current state, not just form saves; dirty means different from the loaded snapshot, not touched), instant first paint (render the shell, load data async, skeletons), perceived performance (prefetch on intent, debounce/throttle, cancel stale requests, avoid waterfalls, lazy widgets), large-list rendering (infinite scroll/pagination, virtualization, skeletons, progressive loading), optimistic UI with rollback, visual restraint (one card level, spacing before borders, one elevation scale), icon actions (repeated row/card actions are icon-only buttons with aria-label plus title, not text labels), responsive/adaptive layout (fluid units, breakpoints, no overlap/overflow, viewport meta, touch targets), variable-length text (min-width:0 in flex/grid, wrap vs truncate, long unbroken strings, worst-case content checks), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
|
|
6
|
-
"updated": "2026-07-
|
|
7
|
-
"cliVersion": "1.32.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-07-31T16:26:45+02:00",
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
|
+
"sha": "39a47d023d64ea1bb9b72fa872276b617cb8c85d910e5b8fb2f23d4ae44f5ef5"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Git & contribution policy (senior engineering standards).",
|
|
6
6
|
"updated": "2026-07-16T22:44:02+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "e6dfbc33884000d9d25841bd9c5a84d6558ffd374882cb7b34451eb2cebc2161"
|
|
9
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: security-policy
|
|
3
|
-
description: Application and AI-agent security - secrets management, authentication and authorization (least privilege), OWASP Top 10 mitigations, transport and crypto baseline, secure logging, and agent/MCP/tool-use safety (prompt injection, untrusted tool output, permission boundaries). Use when handling secrets, auth, permissions, untrusted data or tool output, or any security-sensitive code, config, or infrastructure.
|
|
3
|
+
description: Application and AI-agent security - secrets management, authentication and authorization (least privilege), credential flows (sign-in, sign-up that establishes the session, password reset, 2FA, and rate limiting per IP and per account), OWASP Top 10 mitigations, transport and crypto baseline, secure logging, and agent/MCP/tool-use safety (prompt injection, untrusted tool output, permission boundaries). Use when handling secrets, auth, login or registration screens, permissions, untrusted data or tool output, or any security-sensitive code, config, or infrastructure.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Security Policy
|
|
@@ -37,6 +37,42 @@ description: Application and AI-agent security - secrets management, authenticat
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+
## Credential Flows (Sign-In, Sign-Up, Reset, 2FA)
|
|
41
|
+
|
|
42
|
+
These four screens are one system: an attacker who cannot guess a password will try to register, reset, or brute-force a second factor instead. Build them together.
|
|
43
|
+
|
|
44
|
+
### Sign-up establishes the session
|
|
45
|
+
|
|
46
|
+
- A successful registration already proves the credentials: create the session there and land the user in the app. Never redirect them to the sign-in form to retype what they just typed.
|
|
47
|
+
- Keep email verification asynchronous. Let them in unverified, ask for confirmation, and gate only the actions that genuinely need a verified address (billing, invites, outbound mail).
|
|
48
|
+
- The exception is an account created by someone else (admin provisioning, an approval queue). Then say so in the code, because the redirect looks like the defect.
|
|
49
|
+
|
|
50
|
+
### Password recovery is part of every password login
|
|
51
|
+
|
|
52
|
+
- Every sign-in form with a password field needs a visible recovery entry point. A login screen with no way out of a forgotten password is an incomplete flow, not a simpler one.
|
|
53
|
+
- The request step answers identically whether or not the account exists, and takes the same time. "If that address has an account, we sent a link" is the whole response; never confirm or deny.
|
|
54
|
+
- The token is high-entropy, single-use, stored hashed, and expires in 15 to 60 minutes. Issuing a new one invalidates the previous one.
|
|
55
|
+
- On a successful reset: consume the token, rotate the session, and invalidate every other active session and refresh token for that account. Notify the account by email that the password changed.
|
|
56
|
+
- Never send the new password by email, and never embed credentials in the link. The link proves control of the address, nothing more.
|
|
57
|
+
|
|
58
|
+
### Rate-limit by IP AND by account
|
|
59
|
+
|
|
60
|
+
- Limit both dimensions on every credential endpoint: sign-in, sign-up, password reset request, reset confirmation, email verification, 2FA/OTP verification, and any "does this identifier exist" helper the sign-in page calls.
|
|
61
|
+
- By IP stops the broad sweep. By account or identifier stops the slow distributed attack that spreads one attempt per IP across a botnet, which the IP limit never sees. One without the other is not rate limiting.
|
|
62
|
+
- Count FAILURES, not requests, so a person typing their password wrong twice is not treated like an attack while a scripted run is stopped early.
|
|
63
|
+
- Back off exponentially and answer `429` with `Retry-After`. Keep every counter server-side; a client-held attempt count is decoration.
|
|
64
|
+
- Cap second factors hard: a handful of attempts per code, then invalidate the code and require a new one. OTP codes are single-use with a short TTL, and backup codes are single-use and stored hashed.
|
|
65
|
+
- After a threshold of failures, lock the account temporarily and tell the owner by email. An unbounded lock is a denial-of-service someone else can trigger, so prefer a timed lock with a clear unlock path.
|
|
66
|
+
- Derive the client IP from the trusted proxy chain, never from a raw client-supplied header. `X-Forwarded-For` is attacker-controlled unless your edge rewrites it.
|
|
67
|
+
- The limiter must not become an oracle: an unknown account and a known one get the same response shape, status, and timing. Always run the password hash, even when the user does not exist, so the timing does not answer the question the error message refused to.
|
|
68
|
+
- Registration and any endpoint that sends mail also carry a cost and abuse surface. Limit them per IP, per address, and globally.
|
|
69
|
+
|
|
70
|
+
### Uniform answers
|
|
71
|
+
|
|
72
|
+
- Wrong password, unknown account, locked account, and unverified account all return the same generic failure to the client. The specifics belong in the server log, not the response.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
40
76
|
## OWASP Top 10 Baseline
|
|
41
77
|
|
|
42
78
|
- Injection (SQL/NoSQL/command/LDAP): use parameterized queries and safe APIs; never build queries or shell commands by string concatenation (query specifics in database-expert).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Application and AI-agent security: secrets, authn/authz (least privilege), OWASP Top 10, transport/crypto baseline, secure logging, and agent/MCP/tool-use safety.",
|
|
6
|
-
"updated": "2026-
|
|
7
|
-
"cliVersion": "1.32.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-07-31T16:26:45+02:00",
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
|
+
"sha": "e5e7ac2791919e4b8483115ee8b83800c5ebdf4eebde019650d974b400417c55"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Create new skills, modify and improve existing skills, and measure skill performance with evals and benchmarks.",
|
|
6
6
|
"updated": "2026-07-29T01:18:36+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "13d24c217bdb6fe83fe16835d8f5c3d397a9f3338f16876c61ef96e97f34c90a"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
|
|
6
6
|
"updated": "2026-07-30T20:10:24+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "2f82288d83dca3676d6bc49955c89477cc298f77fa56b4e414cd95ff858fa261"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Concise, realistic technical copy - UI microcopy, descriptions, hints, empty/error states, and README/doc prose that informs without over-explaining or restating the obvious, and never uses a typographic dash.",
|
|
6
6
|
"updated": "2026-07-28T20:30:23+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "a4b792103eb1f9dad93b9d70ea79dc18fe9cbbc318facf5adb47ae5907d842f9"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Test strategy, coverage gates, deterministic tests, mocking discipline, regression-first bug fixing, and test-suite organization (layout by type/domain, mirrored paths, file naming, fixture/helper placement).",
|
|
6
6
|
"updated": "2026-06-16T17:11:49+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, schema consistency, and safe client-facing error handling.",
|
|
6
6
|
"updated": "2026-07-22T01:41:06+02:00",
|
|
7
|
-
"cliVersion": "1.32.
|
|
7
|
+
"cliVersion": "1.32.4",
|
|
8
8
|
"sha": "d937df0052d1ec151728a28f6567744d9e7ca0a65d84b4d5c9a697f1e40d704f"
|
|
9
9
|
}
|
package/bin/checksums.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enigma-darwin-arm64": "
|
|
3
|
-
"enigma-linux-arm64": "
|
|
4
|
-
"enigma-linux-x64": "
|
|
5
|
-
"enigma-win32-x64.exe": "
|
|
2
|
+
"enigma-darwin-arm64": "62e27c388131ff0ea2095e60a85ab8d5172fc6cb548bd4890c7db22778e92e58",
|
|
3
|
+
"enigma-linux-arm64": "491e5d5856333e28c6d05213cd320622d3b5277f7502c5a9d399e93996c7fc65",
|
|
4
|
+
"enigma-linux-x64": "60f16b62280f94415edf43c45dbc29898ad2d944b5536fdcb98f9654a9b768af",
|
|
5
|
+
"enigma-win32-x64.exe": "a75e6dd25028491d5e6c86a6577a884e6265074c74b037dead0fa46de6635e6a"
|
|
6
6
|
}
|
package/dist/guardrails.js
CHANGED
|
@@ -463,6 +463,267 @@ var BUILTIN_RULES = [
|
|
|
463
463
|
fileCheck: "proc-windows-hide",
|
|
464
464
|
message: "Process spawned without windowsHide. On Windows a console child started by a process that has no console of its own - a daemon, an editor hook, a detached background task - pops a real console window on screen and closes it again, which reads as something crashing. Add `windowsHide: true` to the options object; it is inert on macOS and Linux, and inert on Windows when the parent already has a console, so it is safe on every call that is not deliberately opening a terminal for the user. For one that IS (a login flow that must show a terminal), mark the call with an `enigma:` note.",
|
|
465
465
|
severity: "block"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: "fe-mobile-drawer-full-width",
|
|
469
|
+
label: "An off-canvas panel fills the phone screen",
|
|
470
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm"],
|
|
471
|
+
excludeFiles: [
|
|
472
|
+
"*.test.*",
|
|
473
|
+
"*.spec.*",
|
|
474
|
+
"**/tests/**",
|
|
475
|
+
"**/__tests__/**",
|
|
476
|
+
"**/stories/**",
|
|
477
|
+
"*.stories.*",
|
|
478
|
+
"*.min.js",
|
|
479
|
+
"**/dist/**",
|
|
480
|
+
"**/build/**",
|
|
481
|
+
"**/node_modules/**",
|
|
482
|
+
"**/vendor/**",
|
|
483
|
+
"dist/**",
|
|
484
|
+
"build/**",
|
|
485
|
+
"node_modules/**",
|
|
486
|
+
"vendor/**"
|
|
487
|
+
],
|
|
488
|
+
scope: "file",
|
|
489
|
+
// An off-canvas panel (fixed/absolute, pinned for the full height) whose width is a fixed
|
|
490
|
+
// desktop size with no full-width base. Utility-class frameworks only: the same defect in
|
|
491
|
+
// a stylesheet lives inside a @media block, which a line scanner cannot see.
|
|
492
|
+
// PRECISION, measured over the reference corpus: the width must be a PANEL width - 100px
|
|
493
|
+
// or more, 10rem or more, a Tailwind step of 40 (10rem) or more, or a fraction of the
|
|
494
|
+
// viewport. Every false positive found was a hairline (`w-px` chart cursors, a `w-px`
|
|
495
|
+
// resize handle, a `before:w-0.5` guide line), and that one bound removed all of them.
|
|
496
|
+
// The negative lookahead carries the "unless the design says otherwise" cases: an
|
|
497
|
+
// already-full-width base, a width capped by `max-w-full`, and a panel that is hidden on
|
|
498
|
+
// phones anyway (the desktop half of a hidden/drawer pair).
|
|
499
|
+
pattern: "^(?!.*enigma:)(?!.*(?:\\bw-full\\b|\\bmax-w-full\\b|\\bw-screen\\b|\\bhidden\\s+(?:sm|md|lg|xl):|\\bmd:hidden\\b))(?:.*\\b(?:fixed|absolute)\\b[^\"'`]*\\b(?:inset-y-0|h-full|h-screen)\\b[^\"'`]*\\bw-(?:\\[(?:\\d{3,}px|\\d{2,}rem)\\]|[4-9]\\d\\b|\\d{3}\\b|\\d/\\d\\b)|.*\\bw-(?:\\[(?:\\d{3,}px|\\d{2,}rem)\\]|[4-9]\\d\\b|\\d{3}\\b|\\d/\\d\\b)[^\"'`]*\\b(?:fixed|absolute)\\b[^\"'`]*\\b(?:inset-y-0|h-full|h-screen)\\b)",
|
|
500
|
+
absent: "enigma:allow-partial-drawer",
|
|
501
|
+
message: "Off-canvas panel with a desktop width. On a phone a sidebar, drawer or nav panel takes the WHOLE screen - `w-full` as the base, with the desktop width added at a breakpoint (`w-full md:w-80`) or capped by `max-w-*`. A 320px panel on a 360px screen leaves a sliver of dead content behind it, and a panel wider than the viewport is simply cut off. Keep it dismissible: a close control, the backdrop, and Escape. If the design deliberately wants a partial-width panel on mobile, put an `enigma:` note on the line or `enigma:allow-partial-drawer` in the file (frontend-policy).",
|
|
502
|
+
severity: "block",
|
|
503
|
+
skill: "frontend-policy"
|
|
504
|
+
},
|
|
505
|
+
// AUTH. Three defects an agent building a sign-in flow reproduces constantly, each with a
|
|
506
|
+
// file-local signature. They fire only in files NAMED for the flow they belong to
|
|
507
|
+
// (*login*, *register*, *2fa*, ...), which is what keeps them off the rest of a codebase:
|
|
508
|
+
// "password" and "redirect to /login" appear everywhere, but not in a file called login.tsx
|
|
509
|
+
// that is not the sign-in surface. The semantic half of each - token lifetime, lockout
|
|
510
|
+
// policy, what a reset email may reveal - has no signature and lives in security-policy.
|
|
511
|
+
{
|
|
512
|
+
id: "auth-password-reset-entry",
|
|
513
|
+
label: "Login form offers a way to recover the password",
|
|
514
|
+
ignoreFileCase: true,
|
|
515
|
+
// Named for the flow, either way a project spells it: in the FILE name (login-form.tsx)
|
|
516
|
+
// or in the DIRECTORY (app/(auth)/login/page.tsx, the Next App Router shape - a basename
|
|
517
|
+
// glob would miss every one of those). Dir globs are listed twice because `**/x/**` does
|
|
518
|
+
// not match a root-level `x/`, the same gotcha the excludes above carry.
|
|
519
|
+
files: [
|
|
520
|
+
"*login*.tsx",
|
|
521
|
+
"*login*.jsx",
|
|
522
|
+
"*login*.vue",
|
|
523
|
+
"*login*.svelte",
|
|
524
|
+
"*login*.astro",
|
|
525
|
+
"*login*.html",
|
|
526
|
+
"*signin*.tsx",
|
|
527
|
+
"*signin*.jsx",
|
|
528
|
+
"*signin*.vue",
|
|
529
|
+
"*signin*.svelte",
|
|
530
|
+
"*signin*.astro",
|
|
531
|
+
"*signin*.html",
|
|
532
|
+
"*sign-in*.tsx",
|
|
533
|
+
"*sign-in*.jsx",
|
|
534
|
+
"*sign-in*.vue",
|
|
535
|
+
"*sign-in*.svelte",
|
|
536
|
+
"*sign-in*.astro",
|
|
537
|
+
"*sign-in*.html",
|
|
538
|
+
"**/login/**",
|
|
539
|
+
"login/**",
|
|
540
|
+
"**/signin/**",
|
|
541
|
+
"signin/**",
|
|
542
|
+
"**/sign-in/**",
|
|
543
|
+
"sign-in/**"
|
|
544
|
+
],
|
|
545
|
+
excludeFiles: [
|
|
546
|
+
"*.test.*",
|
|
547
|
+
"*.spec.*",
|
|
548
|
+
"**/tests/**",
|
|
549
|
+
"**/__tests__/**",
|
|
550
|
+
"**/stories/**",
|
|
551
|
+
"*.stories.*",
|
|
552
|
+
"**/dist/**",
|
|
553
|
+
"**/build/**",
|
|
554
|
+
"**/node_modules/**",
|
|
555
|
+
"**/vendor/**",
|
|
556
|
+
"dist/**",
|
|
557
|
+
"build/**",
|
|
558
|
+
"node_modules/**",
|
|
559
|
+
"vendor/**"
|
|
560
|
+
],
|
|
561
|
+
scope: "file",
|
|
562
|
+
// The password field is what makes this THE sign-in surface rather than a wrapper or a
|
|
563
|
+
// route file; a login page that only renders <LoginForm/> has no password field and is
|
|
564
|
+
// correctly left alone (the form itself is the file that must carry the link).
|
|
565
|
+
pattern: `type=["']password["']|type=\\{["']password["']\\}`,
|
|
566
|
+
// Any recovery affordance clears the file: the link, the route, or a handler named for it.
|
|
567
|
+
absent: "forgot|reset[-_ ]?password|password[-_ ]?reset|recover|olvid|recuperar|magic[-_ ]?link|enigma:allow-no-reset",
|
|
568
|
+
message: 'Sign-in form with no way out of a forgotten password. Every login form needs a visible "Forgot your password?" entry point next to the password field, leading to a real reset flow: ask for the identifier, always answer the same way (never reveal whether the account exists), email a single-use token that expires in ~15-60 minutes, and on success invalidate that token plus every other active session. If this screen is deliberately reset-less (an internal tool, SSO-only, a passwordless magic-link form), mark the file with an `enigma:allow-no-reset` note (frontend-policy, security-policy).',
|
|
569
|
+
severity: "block",
|
|
570
|
+
skill: "security-policy"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: "auth-signup-auto-login",
|
|
574
|
+
label: "Registration signs the user in",
|
|
575
|
+
ignoreFileCase: true,
|
|
576
|
+
files: [
|
|
577
|
+
"*register*.tsx",
|
|
578
|
+
"*register*.jsx",
|
|
579
|
+
"*register*.ts",
|
|
580
|
+
"*register*.js",
|
|
581
|
+
"*register*.vue",
|
|
582
|
+
"*register*.svelte",
|
|
583
|
+
"*register*.astro",
|
|
584
|
+
"*signup*.tsx",
|
|
585
|
+
"*signup*.jsx",
|
|
586
|
+
"*signup*.ts",
|
|
587
|
+
"*signup*.js",
|
|
588
|
+
"*signup*.vue",
|
|
589
|
+
"*signup*.svelte",
|
|
590
|
+
"*signup*.astro",
|
|
591
|
+
"*sign-up*.tsx",
|
|
592
|
+
"*sign-up*.jsx",
|
|
593
|
+
"*sign-up*.ts",
|
|
594
|
+
"*sign-up*.js",
|
|
595
|
+
"*sign-up*.vue",
|
|
596
|
+
"*sign-up*.svelte",
|
|
597
|
+
"*sign-up*.astro",
|
|
598
|
+
"**/register/**",
|
|
599
|
+
"register/**",
|
|
600
|
+
"**/signup/**",
|
|
601
|
+
"signup/**",
|
|
602
|
+
"**/sign-up/**",
|
|
603
|
+
"sign-up/**"
|
|
604
|
+
],
|
|
605
|
+
excludeFiles: [
|
|
606
|
+
"*.test.*",
|
|
607
|
+
"*.spec.*",
|
|
608
|
+
"**/tests/**",
|
|
609
|
+
"**/__tests__/**",
|
|
610
|
+
"**/stories/**",
|
|
611
|
+
"*.stories.*",
|
|
612
|
+
"*.d.ts",
|
|
613
|
+
"**/dist/**",
|
|
614
|
+
"**/build/**",
|
|
615
|
+
"**/node_modules/**",
|
|
616
|
+
"**/vendor/**",
|
|
617
|
+
"dist/**",
|
|
618
|
+
"build/**",
|
|
619
|
+
"node_modules/**",
|
|
620
|
+
"vendor/**"
|
|
621
|
+
],
|
|
622
|
+
scope: "file",
|
|
623
|
+
// Only a PROGRAMMATIC redirect to the sign-in route counts. An `href="/login"` is the
|
|
624
|
+
// "already have an account?" link every sign-up form carries and is not the defect, so
|
|
625
|
+
// the pattern requires a navigation CALL - which is the code path that runs after the
|
|
626
|
+
// account is created.
|
|
627
|
+
pattern: "(?:push|replace|redirect|navigate|goto|assign)\\(\\s*[\"'`][^\"'`]*/(?:login|signin|sign-in)\\b|location(?:\\.href)?\\s*=\\s*[\"'`][^\"'`]*/(?:login|signin|sign-in)\\b|(?:push|replace|navigate)\\(\\s*\\{[^}]*name:\\s*[\"'`](?:login|signin|sign-in)[\"'`]",
|
|
628
|
+
// Any session-establishing call in the file means the redirect is some other path
|
|
629
|
+
// (an already-registered branch, an error case), so the file is cleared.
|
|
630
|
+
absent: "signIn\\(|signInWith|createSession|setSession|startSession|newSession|setAuthCookie|setAuthToken|setToken\\(|setAuth\\(|login\\(|logIn\\(|authenticate\\(|session\\.save|sessionStorage\\.setItem\\([\"'`](?:token|session)|cookies\\(\\)\\.set|setUser\\(|enigma:allow-login-redirect",
|
|
631
|
+
message: "Registration sends the user to the sign-in screen instead of signing them in. A successful sign-up already proves the credentials: establish the session right there and land the user in the app. Keep email verification asynchronous (let them in, ask them to confirm, and gate only the actions that need a verified address) rather than parking them on a login form to type what they just typed. If this redirect is deliberate (an admin creating someone else's account, an approval queue), mark the file with an `enigma:allow-login-redirect` note (backend-policy, security-policy).",
|
|
632
|
+
severity: "block",
|
|
633
|
+
skill: "security-policy"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: "auth-rate-limit",
|
|
637
|
+
label: "Rate-limit the credential endpoints",
|
|
638
|
+
ignoreFileCase: true,
|
|
639
|
+
files: [
|
|
640
|
+
"*login*.ts",
|
|
641
|
+
"*login*.js",
|
|
642
|
+
"*login*.mts",
|
|
643
|
+
"*login*.cts",
|
|
644
|
+
"*login*.py",
|
|
645
|
+
"*signin*.ts",
|
|
646
|
+
"*signin*.js",
|
|
647
|
+
"*sign-in*.ts",
|
|
648
|
+
"*sign-in*.js",
|
|
649
|
+
"*signin*.py",
|
|
650
|
+
"*sign-in*.py",
|
|
651
|
+
"*register*.ts",
|
|
652
|
+
"*register*.js",
|
|
653
|
+
"*signup*.ts",
|
|
654
|
+
"*signup*.js",
|
|
655
|
+
"*sign-up*.ts",
|
|
656
|
+
"*sign-up*.js",
|
|
657
|
+
"*register*.py",
|
|
658
|
+
"*signup*.py",
|
|
659
|
+
"*sign-up*.py",
|
|
660
|
+
"*2fa*.ts",
|
|
661
|
+
"*2fa*.js",
|
|
662
|
+
"*2fa*.py",
|
|
663
|
+
"*mfa*.ts",
|
|
664
|
+
"*mfa*.js",
|
|
665
|
+
"*mfa*.py",
|
|
666
|
+
"*otp*.ts",
|
|
667
|
+
"*otp*.js",
|
|
668
|
+
"*otp*.py",
|
|
669
|
+
"*forgot-password*.ts",
|
|
670
|
+
"*forgot-password*.js",
|
|
671
|
+
"*forgot-password*.py",
|
|
672
|
+
"*reset-password*.ts",
|
|
673
|
+
"*reset-password*.js",
|
|
674
|
+
"*reset-password*.py",
|
|
675
|
+
"**/login/**",
|
|
676
|
+
"login/**",
|
|
677
|
+
"**/signin/**",
|
|
678
|
+
"signin/**",
|
|
679
|
+
"**/sign-in/**",
|
|
680
|
+
"sign-in/**",
|
|
681
|
+
"**/register/**",
|
|
682
|
+
"register/**",
|
|
683
|
+
"**/signup/**",
|
|
684
|
+
"signup/**",
|
|
685
|
+
"**/sign-up/**",
|
|
686
|
+
"sign-up/**",
|
|
687
|
+
"**/2fa/**",
|
|
688
|
+
"2fa/**",
|
|
689
|
+
"**/mfa/**",
|
|
690
|
+
"mfa/**",
|
|
691
|
+
"**/otp/**",
|
|
692
|
+
"otp/**",
|
|
693
|
+
"**/forgot-password/**",
|
|
694
|
+
"forgot-password/**",
|
|
695
|
+
"**/reset-password/**",
|
|
696
|
+
"reset-password/**"
|
|
697
|
+
],
|
|
698
|
+
excludeFiles: [
|
|
699
|
+
"*.test.*",
|
|
700
|
+
"*.spec.*",
|
|
701
|
+
"**/tests/**",
|
|
702
|
+
"**/__tests__/**",
|
|
703
|
+
"test_*.py",
|
|
704
|
+
"*_test.py",
|
|
705
|
+
"*.d.ts",
|
|
706
|
+
"**/dist/**",
|
|
707
|
+
"**/build/**",
|
|
708
|
+
"**/node_modules/**",
|
|
709
|
+
"**/vendor/**",
|
|
710
|
+
"dist/**",
|
|
711
|
+
"build/**",
|
|
712
|
+
"node_modules/**",
|
|
713
|
+
"vendor/**"
|
|
714
|
+
],
|
|
715
|
+
scope: "file",
|
|
716
|
+
// A server-side handler for the flow: the route export/registration, a framework
|
|
717
|
+
// decorator, or a "use server" module (a Next server action reachable from the sign-in
|
|
718
|
+
// page is an unauthenticated endpoint like any other, and it is how App Router projects
|
|
719
|
+
// write this). A client component calling fetch() is deliberately not matched - it
|
|
720
|
+
// cannot enforce a limit, and the file that must is the one defining the endpoint.
|
|
721
|
+
pattern: `export\\s+(?:async\\s+)?function\\s+(?:POST|PUT|PATCH)\\b|export\\s+const\\s+(?:POST|PUT|PATCH)\\s*[:=]|\\b(?:router|app|api|server|fastify)\\.(?:post|put|patch)\\s*\\(|@(?:app|router|bp|blueprint)\\.(?:post|route)\\s*\\(|@Post\\s*\\(|^["']use server["']`,
|
|
722
|
+
// Cleared by any limiter in the file, whatever the library or the wrapper name.
|
|
723
|
+
absent: "rate[-_]?limit|ratelimit|Ratelimit|RateLimiter|limiter|throttle|slowDown|slow_down|bottleneck|arcjet|leaky|token[-_]?bucket|attempts?[-_]?(?:left|remaining|count)|lockout|too[-_ ]?many[-_ ]?requests|429|enigma:allow-unlimited-auth",
|
|
724
|
+
message: "Credential endpoint with no rate limiting. Login, registration, password reset and every 2FA/OTP verification are guessing surfaces: limit them BY IP (blunt, stops the broad sweep) AND BY ACCOUNT or identifier (stops the slow distributed attack the IP limit misses), count failures rather than requests, back off exponentially, and answer 429 with Retry-After. Keep the accounting server-side and identical for an unknown account, so the limiter itself does not become an account-existence oracle. If the limit is enforced upstream (gateway, middleware, WAF), note it in the file with an `enigma:allow-unlimited-auth` marker (security-policy, backend-policy).",
|
|
725
|
+
severity: "block",
|
|
726
|
+
skill: "security-policy"
|
|
466
727
|
}
|
|
467
728
|
];
|
|
468
729
|
var PROJECT_CHECKS = {
|
|
@@ -533,10 +794,10 @@ function readPkgDeps(root) {
|
|
|
533
794
|
return null;
|
|
534
795
|
}
|
|
535
796
|
}
|
|
536
|
-
function globToRegExp(glob) {
|
|
797
|
+
function globToRegExp(glob, ignoreCase = false) {
|
|
537
798
|
const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
538
799
|
const body = esc.replace(/\*\*/g, " ").replace(/\*/g, "[^/]*").replace(/ /g, ".*").replace(/\?/g, "[^/]");
|
|
539
|
-
return new RegExp(glob.includes("/") ? `^${body}$` : `(^|/)${body}
|
|
800
|
+
return new RegExp(glob.includes("/") ? `^${body}$` : `(^|/)${body}$`, ignoreCase ? "i" : "");
|
|
540
801
|
}
|
|
541
802
|
function guardrailsConfigPath() {
|
|
542
803
|
return process.env.ENIGMA_GUARDRAILS_CONFIG || join(homedir(), ".enigma-guardrails.json");
|
|
@@ -581,8 +842,8 @@ function checkFile(file, content, projectRoot) {
|
|
|
581
842
|
const norm = file.replace(/\\/g, "/");
|
|
582
843
|
const out = [];
|
|
583
844
|
for (const rule of loadRules()) {
|
|
584
|
-
if (!rule.files.some((g) => globToRegExp(g).test(norm))) continue;
|
|
585
|
-
if (rule.excludeFiles?.some((g) => globToRegExp(g).test(norm))) continue;
|
|
845
|
+
if (!rule.files.some((g) => globToRegExp(g, rule.ignoreFileCase).test(norm))) continue;
|
|
846
|
+
if (rule.excludeFiles?.some((g) => globToRegExp(g, rule.ignoreFileCase).test(norm))) continue;
|
|
586
847
|
const base = { ruleId: rule.id, severity: rule.severity, file: norm, message: rule.message, skill: rule.skill };
|
|
587
848
|
if (rule.scope === "file" && rule.maxBytes) {
|
|
588
849
|
const bytes = Buffer.byteLength(content, "utf8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.4",
|
|
4
4
|
"description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|