Documentation
Reference
Dual-path architecture (targets + repos), profiles, CLI, env knobs, and the ownership-first security model.
Overview
Hackly is an orchestration layer for authorized attack-surface and
application security scanning. You inventory targets, prove ownership, scan GitHub
repositories via PAT, run soft rate-limited jobs, and review normalized findings in Filament.
Target (verified) Repository (GitHub PAT)
│ │
├─ optional link ──────────────────────┤
│ │
├─ Scan (DAST) ├─ RepoScan
│ └─ ScanTasks → Findings │ └─ RepoScanTasks → Findings
│ │ (asset_id nullable)
└─ optional: include linked repos └─ optional: include linked targets
Jobs use the database queue (no Horizon). A queue worker must be
running for scans to execute. Linking Targets ↔ Repositories is optional — each side
can scan on its own.
Requirements
- PHP 8.3+
- Composer
- SQLite (default) or MySQL / PostgreSQL
git (for repository clones)
- Node.js (optional — Vite assets; Filament ships published assets)
Default admin after seed: / · admin@hackly.test · password
Scanner binaries
Target scanners
Optional but recommended. Without them, related tasks fail gracefully and surface
missing-binary errors in the scan UI.
| Binary |
Used for |
| dig, whois |
DNS / WHOIS |
| nmap |
Ports |
| nuclei |
Templates & path discovery |
| zap.sh |
Deep DAST (OWASP ZAP) |
sudo bash scripts/install-scanner-binaries-ubuntu.sh
php artisan hackly:check-binaries
Repository scanners
Needed for GitHub repo scanning. Missing tools cause those tasks to be
skipped (other tasks still run). Composer/OSV and Hackly Laravel
auditors do not require Semgrep/Trivy.
| Binary |
Used for |
| semgrep |
PHP SAST |
| trivy |
SCA |
| gitleaks |
Secrets |
| checkov |
IaC |
| composer |
composer audit |
sudo bash scripts/install-repo-scanners.sh
php artisan hackly:check-binaries
Run hackly:check-binaries as the queue worker user
(e.g. www-data). The install script puts tools on /usr/local/bin
so they are not trapped in a root-only ~/.local.
Targets
Admin UI
- Open Targets → create a domain or IP
- Copy the DNS token → publish the TXT record at your DNS provider
- Verify DNS — must succeed before target scanning
- Start scan — pick a profile; optionally enable Include linked repositories
- Watch Scans for live progress, open findings (HIGH → LOW), export the PDF
- On a target’s detail page: Linked repositories + Scans
Targets do not need a linked repository.
CLI
php artisan hackly:scan example.com --profile=standard
php artisan hackly:scan example.com --include-repos
Repositories
Admin UI
- Open GitHub tokens → add a PAT → Validate
- Open Repositories → add
owner/repo (checked against the GitHub API)
- Optionally link one or more Targets
- Scan now — pick a repo profile; optionally Include linked targets (queues deep DAST on each verified linked Target)
- Review Repo scans and Findings on the repository page
Repo findings are stored on the repository (asset_id may be null).
PDF export is for target scans only.
GitHub token permissions
| Type |
Permission |
| Classic PAT |
repo (private) or public_repo (public only) |
| Fine-grained |
Contents: Read + Metadata: Read on selected repos |
Post-processing
- Cross-tool deduplication (e.g. same package + CVE from Trivy and OSV → one finding)
- PHP reachability: namespace usage in
app/, routes/, config/; require-dev → unreachable; Laravel package auto-discovery
- Noise filters: placeholder secrets, path out of app scope, configurable rule suppressions
- Findings store
reachability, confidence, noise_filtered, and tools[]
CLI
php artisan hackly:repo-scan owner/repo --profile=standard
php artisan hackly:repo-scan owner/repo --include-targets
Independent scan modes
| Goal |
UI |
CLI |
| Target only |
Start scan (toggle off) |
hackly:scan example.com |
| Target + repos |
Include linked repositories |
hackly:scan … --include-repos |
| Repo only |
Scan now (toggle off) |
hackly:repo-scan owner/repo |
| Repo + targets |
Include linked targets |
hackly:repo-scan … --include-targets |
Laravel live pentest runs only when the repo scan includes linked targets.
Repo-only scans skip it.
Scan profiles
Target (DAST)
| Profile |
Tasks |
| quick |
dns_info, port_scan, tech_fingerprint |
| standard |
+ subdomain_enum, path_discovery, nuclei_owasp |
| deep |
+ zap_baseline (cooldown between deep runs on the same target) |
Repository (SAST / SCA)
| Profile |
Tasks |
| quick |
gitleaks_secrets, composer_osv, laravel_php_audit |
| standard |
+ semgrep_sast, trivy_sca, laravel_live_pentest* |
| deep |
+ checkov_iac |
* Live pentest only when linked targets are included. Findings are normalized to
LOW / MEDIUM / HIGH.
Commands
Scans are started manually from the admin UI or CLI — there is no automatic hourly/nightly scan schedule.
| Command |
Purpose |
| hackly:check-binaries |
Verify target + repo scanner binaries |
| hackly:scan {target} |
Target scan (--profile=, --include-repos) |
| hackly:repo-scan {repo} |
Repo scan (--profile=, --include-targets) |
| hackly:cleanup-outputs |
Delete old raw scanner outputs |
| hackly:dispatch-due |
Re-dispatch leftover pending tasks |
| queue:work |
Process scan jobs |
| schedule:work |
Hourly cleanup of old outputs (or cron schedule:run) |
Configuration
Primary config: config/hackly.php — override via .env.
QUEUE_CONNECTION=database
CACHE_STORE=database
# Safety
HACKLY_ALLOWLIST_ONLY=false
HACKLY_ALLOWLIST=example.com,203.0.113.10
HACKLY_ALLOW_PRIVATE_TARGETS=true
# Soft rate limits
HACKLY_PER_TARGET_PER_MINUTE=2
HACKLY_GLOBAL_CONCURRENT=5
HACKLY_JITTER_SECONDS=5
HACKLY_TASK_SPACING_SECONDS=10
HACKLY_DEEP_COOLDOWN_HOURS=24
HACKLY_QUIET_HOURS_ENABLED=false
# Job timeout must exceed longest scanner (ZAP ~900s)
HACKLY_JOB_TIMEOUT=960
DB_QUEUE_RETRY_AFTER=1020
# Target binary paths
HACKLY_NMAP=nmap
HACKLY_NUCLEI=nuclei
# macOS: HACKLY_ZAP=/Applications/ZAP.app/Contents/Java/zap.sh
HACKLY_ZAP=zap.sh
# GitHub repository scanning
HACKLY_GIT=git
HACKLY_COMPOSER=composer
HACKLY_SEMGREP=semgrep
HACKLY_TRIVY=trivy
HACKLY_GITLEAKS=gitleaks
HACKLY_CHECKOV=checkov
# HACKLY_SEMGREP_CONFIG=p/php
# HACKLY_OSV_ENABLED=true
# HACKLY_REPO_HIDE_UNREACHABLE=false
# HACKLY_REPO_DROP_NOISE=false
Security model
- Target scans require ownership verification (DNS TXT for domains)
- Repository scans require a GitHub token with read access to that repo
- Linking Targets ↔ Repositories is optional; scans remain independently startable
- Optional allowlist and private-IP blocking for Targets
- Soft rate limits, jitter, task spacing, and quiet hours
- Deep target profile has a cooldown between runs
- GitHub PATs are stored encrypted at rest (
APP_KEY) via the GitHub tokens UI
- Repo workspace clones are removed after finalize when
HACKLY_REPO_CLEANUP=true (default)
- Admin accounts support TOTP and email multi-factor authentication
Hackly is an orchestration layer — it does not replace responsible disclosure policies
or scoped pentest agreements. PHP reachability / noise reduction is heuristic
(strong signal for Laravel apps), not a full interprocedural guarantee.
Production tips
- Set
HACKLY_ALLOW_PRIVATE_TARGETS=false
- Prefer
HACKLY_ALLOWLIST_ONLY=true with an explicit allowlist
- Configure real mail for email 2FA (
MAIL_MAILER=…)
- Change the seeded admin password immediately
- Store GitHub PATs only via the GitHub tokens UI
- Run both install scripts on the scan worker host, then
hackly:check-binaries as the queue user
- Run a persistent
queue:work; optionally schedule:work (or cron) for output cleanup
Source and issues:
github.com/andreapollastri/hackly