PHPackages                             jcadima/sigil - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. jcadima/sigil

ActiveProject[CLI &amp; Console](/categories/cli)

jcadima/sigil
=============

Infrastructure hardening manifest engine for LEMP(self hosted) stacks

v1.0.3(2mo ago)25MITPHPPHP ^8.2

Since Mar 17Pushed 2mo agoCompare

[ Source](https://github.com/jcadima/sigil)[ Packagist](https://packagist.org/packages/jcadima/sigil)[ Docs](https://github.com/jcadima/sigil)[ RSS](/packages/jcadima-sigil/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (12)Versions (5)Used By (0)

 [![Sigil — Infrastructure Hardening Manifest Engine](https://camo.githubusercontent.com/cc3fefa18dcd951b89cca67c47519e24c0327513c78a412d72c68e88ef9cc892/68747470733a2f2f6a636164696d612e6465762f696d616765732f736967696c5f62672e706e67)](https://camo.githubusercontent.com/cc3fefa18dcd951b89cca67c47519e24c0327513c78a412d72c68e88ef9cc892/68747470733a2f2f6a636164696d612e6465762f696d616765732f736967696c5f62672e706e67)

Sigil — Infrastructure Hardening Manifest Engine
================================================

[](#sigil--infrastructure-hardening-manifest-engine)

> SIGIL is what `composer audit` would be if it covered your entire infrastructure — Nginx, Docker, PHP, and your database not just your dependencies.

Sigil scans your actual server configuration files, scores findings by severity, generates executable patches, and tracks configuration drift between deploys. It is a CLI-first tool designed for developers and backend engineers managing self-hosted LEMP stacks.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Commands](#commands)
    - [scan](#sigil-scan)
    - [enforce](#sigil-enforce)
    - [snapshot](#sigil-snapshot)
    - [drift](#sigil-drift)
    - [rules](#sigil-rules)
- [How Fixes Work](#how-fixes-work)
- [Severity Model](#severity-model)
- [Rule Library](#rule-library)
- [Project Structure](#project-structure)
- [Contributing](#contributing)
- [License](#license)

---

Requirements
------------

[](#requirements)

- PHP 8.2+
- Composer

Sigil uses four [standalone Symfony components](https://symfony.com/components) not the full framework. It runs on any Linux server that has PHP in `$PATH`.

---

Installation
------------

[](#installation)

Install globally via Composer:

```
composer global require jcadima/sigil
```

Find the Composer global bin directory

```
composer global config bin-dir --absolute
```

You'll get a path like:

```
/home/yourusername/.config/composer/vendor/bin
```

### Make sigil available in your terminal

[](#make-sigil-available-in-your-terminal)

You need to add this directory to your **PATH** so your shell can find **sigil** anywhere.

Add this line at the end of your shell profile: **~/.zshrc** or **~/.bashrc**, replacing **yourusername** with your actual **username**

```
export PATH="$PATH:/home/yourusername/.config/composer/vendor/bin"
```

Apply the changes

```
source ~/.bashrc or source ~/.zshrc
```

**VPS / server alternative, symlink to `/usr/local/bin` (no shell profile changes needed):**

```
sudo ln -sf "$(composer global config bin-dir)/sigil" /usr/local/bin/sigil
```

This makes `sigil` available to all users and works in cron jobs, scripts, and CI pipelines without any PATH configuration.

Verify the installation:

```
sigil --version
```

---

Commands
--------

[](#commands)

### `sigil scan`

[](#sigil-scan)

Audits your project against the full rule library. Sigil auto-detects your stack (framework, web server, database engine) from `.env`, `docker-compose.yml`, and the file system no flags required in most cases.

```
sigil scan [path] [options]
```

**Arguments**

ArgumentDefaultDescription`path`current directoryAbsolute or relative path to the project root being scanned**Options**

OptionDescription`--output=cli`Human-readable terminal output with colored severity labels *(default)*`--output=json`Machine-readable JSON to stdout suitable for CI/CD pipelines and the dashboard`--output=patch`Generates unified diff files to `.sigil/patches/` for all patchable findings`--env=production`Override the detected environment context. Affects which rules fire and severity thresholds`--stack=laravel-docker-nginx`Skip auto-detection and declare the stack explicitly`--compat=mariadb`Force the MariaDB rule pack when `.env` has `DB_CONNECTION=mysql` but the image is actually MariaDB**Examples**

```
# Scan the current directory, display results in terminal
sigil scan

# Scan a specific project path
sigil scan /var/www/myapp

# Scan and output JSON (pipe to jq, send to CI, etc.)
sigil scan /var/www/myapp --output=json | jq '.findings[] | select(.severity == "CRITICAL")'

# Generate patch files for reviewable fixes
sigil scan /var/www/myapp --output=patch

# Force environment context if .env detection is ambiguous
sigil scan /var/www/myapp --env=production
```

**Sample output**

```
  SIGIL v1.0.0
  Detected: laravel · docker · nginx · mysql · production
  ─────────────────────────────────────────────────────────

  LARAVEL / PHP
  ✖ [CRITICAL] L001  APP_DEBUG=true (.env line 2)
               → sigil enforce --rule=L001
  ✖ [HIGH]     L012  disable_functions not set exec, shell_exec exposed
               → /etc/php/8.3/fpm/php.ini line 312
  ✓ [PASS]     L003  APP_KEY set
  ✓ [PASS]     L005  CSRF middleware present

  NGINX
  ✖ [CRITICAL] N010  .env not blocked in nginx config
               → Patch available: sigil scan --output=patch
  ✖ [MEDIUM]   N004  HSTS header missing
  ✓ [PASS]     N007  TLSv1/1.1 disabled

  DOCKER
  ✖ [CRITICAL] D002  Docker socket mounted in php-fpm container
  ✖ [HIGH]     D001  php-fpm running as root
  ✖ [HIGH]     D007  Port 3306 bound to 0.0.0.0 on host

  MYSQL
  ✖ [CRITICAL] M001  DB_USERNAME=root app connecting as superuser
  ✓ [PASS]     M004  MySQL 8.0 within support window

  ──────────────────────────────────────────────────────────
  Score: 44/100  Critical: 4  High: 3  Medium: 1  Low: 0
  Auto-fixable: 3 findings  |  run 'sigil enforce'

```

---

### `sigil enforce`

[](#sigil-enforce)

Applies auto-fixes for supported findings. Sigil **always writes a backup** before modifying any file. CRITICAL and HIGH findings are never auto-applied see [How Fixes Work](#how-fixes-work) for the full breakdown.

```
sigil enforce [options]
```

**Options**

OptionDescription`--dry-run`Preview every change that would be made without writing anything`--rule=ID`Apply only the fix for a single rule (e.g. `--rule=L001`)**Examples**

```
# Preview all pending auto-fixes without applying them
sigil enforce --dry-run

# Apply all eligible fixes interactively (prompts for confirmation)
sigil enforce

# Apply a single specific rule fix
sigil enforce --rule=L010

# Target a specific project path
sigil enforce /var/www/myapp --dry-run
```

**Interactive flow**

```
$ sigil enforce

  Backup written to .sigil/backups/php.ini.20260307_174800
  Backup written to .sigil/backups/.env.20260307_174800

  Pending changes:
    L001  .env           → APP_DEBUG=false
    L010  php.ini        → expose_php=Off
    L011  php.ini        → display_errors=Off
    L012  php.ini        → disable_functions=exec,shell_exec,passthru,proc_open,system

  Proceed? [y/N]: y

  ✓ Applied L001  ✓ Applied L010  ✓ Applied L011  ✓ Applied L012

```

Backups are stored in `.sigil/backups/` inside your project root and can be restored manually if needed.

---

### `sigil snapshot`

[](#sigil-snapshot)

Saves the current configuration state as a signed baseline for drift comparison. Run this after a deploy or after applying fixes to record a known-good state.

```
sigil snapshot [path]
```

Snapshots are stored in `.sigil/snapshots/` and are HMAC-signed to detect tampering. Each snapshot captures parsed values from `.env`, `nginx.conf`, `docker-compose.yml`, `php.ini`, and database config files.

```
# Snapshot the current directory
sigil snapshot

# Snapshot a specific project
sigil snapshot /var/www/myapp
```

---

### `sigil drift`

[](#sigil-drift)

Compares the current configuration state against the most recent stored snapshot. Use this after deploys, config changes, or when investigating unexpected changes.

```
sigil drift [path]
```

```
# Check for drift in the current directory
sigil drift

# Check a specific project
sigil drift /var/www/myapp
```

Drift detection flags any configuration value that changed since the last `sigil snapshot`. Removed rules, new environment variables, changed php.ini directives, and modified nginx blocks all appear as drift events.

---

### `sigil rules`

[](#sigil-rules)

Lists all rules available for the detected (or specified) stack, with severity levels and short descriptions.

```
sigil rules [options]
```

**Options**

OptionDescription`--category=`Filter to a single rule categoryAvailable categories: `laravel`, `nginx`, `docker`, `mysql`, `mariadb`, `postgresql`

**Examples**

```
# List all rules for the auto-detected stack
sigil rules

# List only PostgreSQL rules
sigil rules --category=postgresql

# List only Nginx rules
sigil rules --category=nginx
```

---

How Fixes Work
--------------

[](#how-fixes-work)

Sigil uses three distinct remediation modes depending on the severity and type of finding.

### Mode 1 — `sigil enforce` (auto-apply, LOW/INFO only)

[](#mode-1--sigil-enforce-auto-apply-lowinfo-only)

Low-risk findings with safe, deterministic fixes are handled automatically. Sigil writes a backup first, then applies the change.

Auto-fixable rules:

RuleWhat gets fixedL001Sets `APP_DEBUG=false` in `.env`L006Corrects `storage/` directory permissionsL010Sets `expose_php=Off` in `php.ini`L011Sets `display_errors=Off` in `php.ini`L012Sets `disable_functions` in `php.ini`### Mode 2 — `sigil scan --output=patch` (review, then apply)

[](#mode-2--sigil-scan---outputpatch-review-then-apply)

For MEDIUM findings and structural changes to Nginx or Docker config. Sigil generates the diff file you own the apply step.

```
$ sigil scan --output=patch
  Patches written to .sigil/patches/
    → nginx-security-headers.patch
    → nginx-block-env.patch

$ cat .sigil/patches/nginx-block-env.patch
  --- /etc/nginx/sites-available/myapp
  +++ /etc/nginx/sites-available/myapp.patched
  @@ -14,6 +14,10 @@
       root /var/www/myapp/public;
  +    # SIGIL N010 — Block direct .env access
  +    location ~ /\.env {
  +        deny all; return 404;
  +    }

$ patch /etc/nginx/sites-available/myapp  **Note on `unix_socket` auth:** MariaDB enables `unix_socket` authentication for root by default. Sigil treats this as a PASS. The MB001 root-user rule does not fire for a correctly configured MariaDB `unix_socket` setup unlike the equivalent MySQL rule, which would be a false positive here.

If your project has `DB_CONNECTION=mysql` but is actually running MariaDB, use the `--compat=mariadb` flag to force the correct rule pack:

```
sigil scan --compat=mariadb
```

RuleFindingSeverityAuto-FixMB001Root user used as application DB userCRITICALManualMB002MariaDB port 3306 exposed to host networkHIGHManualMB003No SSL/TLS for MariaDB connectionsMEDIUMManualMB004MariaDB version below active security support windowMEDIUMNoMB005`STRICT_TRANS_TABLES` not in `sql_mode` — silent data truncation riskMEDIUMManual### PostgreSQL (PG001–PG007)

[](#postgresql-pg001pg007)

Loaded when `DB_CONNECTION=pgsql` is set, or when the `postgres` image is found in `docker-compose.yml`.

> **Note on `pg_hba.conf`:** This file is often only readable from inside the database container. If Sigil cannot access it, PG003 and PG007 will be skipped and flagged as INFO with a note to run the scan from inside the container.

RuleFindingSeverityAuto-FixPG001Superuser used as application DB user (`DB_USERNAME=postgres`)CRITICALManualPG002PostgreSQL port 5432 exposed to host network on `0.0.0.0`HIGHManualPG003`pg_hba.conf` contains `trust` authentication methodCRITICALManualPG004`ssl=off` in `postgresql.conf` — connections unencryptedMEDIUMManualPG005`log_connections` / `log_disconnections` disabled — no audit trailLOWYesPG006PostgreSQL version below active security support windowMEDIUMNoPG007`pg_hba.conf` allows all hosts (`0.0.0.0/0`) without restrictionHIGHManual---

Project Structure
-----------------

[](#project-structure)

```
sigil/
├── bin/sigil                       # Executable entry point
├── src/
│   ├── Application.php             # Bootstraps console, registers commands
│   ├── Commands/                   # scan, enforce, snapshot, drift, rules
│   ├── Parsers/                    # .env, nginx.conf, docker-compose.yml, php.ini, my.cnf, pg_hba.conf
│   ├── Rules/
│   │   ├── RuleInterface.php       # evaluate(), getSeverity(), getRemediation(), canAutoFix()
│   │   ├── Laravel/                # L001–L012
│   │   ├── Nginx/                  # N001–N011
│   │   ├── Docker/                 # D001–D009
│   │   ├── MySQL/                  # M001–M005
│   │   ├── MariaDB/                # MB001–MB005
│   │   └── PostgreSQL/             # PG001–PG007
│   ├── Fixers/                     # Write layer — separate from Rules
│   ├── Engine/
│   │   ├── ScanContext.php         # Data container passed to every rule
│   │   ├── StackDetector.php       # Auto-detects framework, web server, DB engine
│   │   ├── RuleEngine.php          # Loads rule packs, collects findings
│   │   ├── NvdClient.php           # NIST NVD API v2 with 24hr local cache
│   │   └── SnapshotManager.php     # HMAC-signed snapshots for drift detection
│   └── Reporters/                  # cli, json, patch output renderers
├── stubs/                          # Nginx and php.ini patch templates
└── tests/                          # Unit + integration tests with fixtures

```

The architecture follows a strict three-layer separation:

- **Rules** — read-only. They interrogate `ScanContext` and return findings. They never touch the filesystem.
- **Fixers** — the only layer that writes files. Always backs up before modifying.
- **Reporters** — render `FindingCollection` to the chosen output format.

---

Contributing
------------

[](#contributing)

Contributions are welcome. The most useful contributions are new rules, parser improvements, and bug reports with reproducible fixture cases.

### Getting started

[](#getting-started)

```
git clone https://github.com/jcadima/sigil
cd sigil
composer install
./vendor/bin/phpunit
```

### Writing a new rule

[](#writing-a-new-rule)

Every rule implements `RuleInterface`. The interface has five methods:

```
interface RuleInterface
{
    public function evaluate(ScanContext $context): FindingCollection;
    public function getSeverity(): Severity;
    public function getCategory(): string;
    public function getRemediation(): Remediation;
    public function canAutoFix(): bool;
    public function applyFix(ScanContext $context): FixResult;
}
```

The `evaluate()` method receives the fully parsed `ScanContext` and returns an empty `FindingCollection` on pass, or one with `Finding` objects on failure. Rules never read files directly they always work from the pre-parsed context.

The `applyFix()` method is only called by `EnforceCommand` and only if `canAutoFix()` returns `true`. **Only LOW and INFO severity rules may return `true` from `canAutoFix()`.**

After writing a rule, register it in `RuleEngine.php` and add unit tests under `tests/Unit/Rules//`.

### Fixture-based testing

[](#fixture-based-testing)

Integration tests use fixture directories under `tests/Fixtures/`. Each fixture set is a minimal project structure (`.env`, `nginx.conf`, `docker-compose.yml`, `php.ini`) representing a specific scenario. If your rule requires a new test scenario, add a fixture directory rather than mocking file reads.

### Rule naming conventions

[](#rule-naming-conventions)

CategoryPrefixExample class nameLaravel`L``AppDebugEnabledRule`Nginx`N``EnvNotBlockedRule`Docker`D``DockerSocketMountedRule`MySQL`M``RootAsAppUserRule`MariaDB`MB``SqlModeStrictRule`PostgreSQL`PG``PgHbaTrustAuthRule`### Submitting changes

[](#submitting-changes)

1. Fork the repository and create a branch from `main`
2. Add or modify code with corresponding tests
3. Run the full test suite: `./vendor/bin/phpunit`
4. Open a pull request with a clear description of what the rule detects and why it matters

For bug reports, please include the output of `sigil scan --output=json` (with sensitive values redacted) and the Sigil version (`sigil --version`).

---

License
-------

[](#license)

MIT see [LICENSE](LICENSE).

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance85

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~7 days

Total

4

Last Release

78d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.0.1PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/dd3d82f149b79e91ee3cbb12ea29ce37d8796a788c036db370cb116921f60de7?d=identicon)[jcadima](/maintainers/jcadima)

---

Top Contributors

[![jcadima](https://avatars.githubusercontent.com/u/2554115?v=4)](https://github.com/jcadima "jcadima (7 commits)")

---

Tags

clilaravelsecuritydevopsdockernginxhardening

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jcadima-sigil/health.svg)

```
[![Health](https://phpackages.com/badges/jcadima-sigil/health.svg)](https://phpackages.com/packages/jcadima-sigil)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

19564.8M1.6k](/packages/drupal-core)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6941.5M396](/packages/drupal-core-recommended)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M517](/packages/shopware-core)[whatsdiff/whatsdiff

See what's changed in your project's dependencies

761.1k](/packages/whatsdiff-whatsdiff)[jolicode/castor

A lightweight and modern task runner. Automate everything. In PHP.

54642.4k4](/packages/jolicode-castor)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
