Documentation
Reference
Targets, repositories, how a link between them changes DAST and penetration tests, compliance scope, profiles, CLI, the ownership-first security model, and an honest look at Aikido, Detectify and the tools around them.
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 (DNS-verified) Repository (GitHub PAT)
│ │
├─ link, from either side or a pentest ┤
│ │
├─ Scan (DAST) ├─ RepoScan
│ uses routes from the last clone │ remembers those routes
├─ Penetration test │
│ standing DNS + deep linked scan │
└─ Compliance subject └─ same line when the two are linked
Jobs use the database queue (no Horizon). A queue worker must be
running for scans to execute. Linking is optional — each side can still be scanned
on its own. When you do link them, DAST, penetration tests and compliance use the pair.
Compared with other tools
Hackly is a self-hosted orchestrator for the domains and GitHub repositories a Laravel team already operates.
Aikido,
Detectify,
Snyk, Semgrep, GitHub Advanced Security and DefectDojo overlap with it and then diverge.
This is a fit comparison, written from Hackly's side. It is not a scan-off. The commercial products move faster than this page.
Aikido
Aikido is a commercial application-security platform. One product covers SAST across major languages, dependency scanning with reachability, secrets, malware and license risk in open source, infrastructure as code, containers, cloud posture, dynamic testing including authenticated DAST, API discovery and fuzzing, attack-surface monitoring, AI pentesting, autofix pull requests, and runtime protection. There is a free tier. Local scanning and a full on-prem deployment exist on paid and government plans. The default is hosted: Aikido clones repositories into short-lived environments, scans them, and throws the clone away.
On the things that make a security platform, Aikido is ahead. It covers languages a PHP ruleset and a set of Laravel checks do not. It opens a pull request with a proposed fix. Hackly can copy a finding as a prompt, and the repository stays as it was. Aikido looks at cloud accounts, Kubernetes, containers and runtime traffic. Hackly does not see AWS, GCP, Azure or a cluster. Authenticated DAST and API fuzzing are products there. A Hackly penetration test can submit one declared test account, once, on a host whose target is already DNS-verified. Aikido reports inside CI. Hackly runs when someone starts a scan: there is no pull-request check and no recurring scan schedule.
Noise handling is smaller here on purpose. Findings are deduplicated across tools, some unreachable PHP dependencies are marked, and placeholder secrets are dropped. That is a heuristic for Laravel apps, not a general false-positive engine. Aikido advertises aggressive noise reduction. This page does not repeat that as a measured result.
Part of Aikido's surface testing is Nuclei-based. Hackly runs Nuclei too, along with nmap, OWASP ZAP, Nikto and WPScan. The engines are not the difference. The difference is everything wrapped around them, and whose machine the job runs on.
Hackly is the tighter fit when the findings have to stay on a server you administer and the apps are Laravel. The license is MIT. Findings, GitHub tokens and raw scanner output stay there. You pay with the host, the binaries and a queue worker. Every domain scan waits for a DNS TXT record that proves control of that name. Aikido's attack-surface module watches assets you connect. Hackly refuses a hostname you cannot publish DNS for. That gate is the reason the tool exists, and it is a limitation when the asset is a tenant you do not control.
Repository scanning is aimed at PHP and Laravel: Composer audit, OSV, abandoned packages, Laravel outside security support, the Checkpoint checks, Semgrep with p/php, Trivy and Gitleaks. A Python or Go monorepo gets a thin result. The external scan is rate-limited on purpose, so a deep run is slow. Aikido's hosted DAST is faster to turn on, and nobody installs ZAP.
The compliance pages score evidence from findings Hackly already stored. They are a worksheet for an auditor. They are not a certification, and they do not collect policies, vendors or access reviews. Aikido is not that product either.
Detectify
Detectify is a hosted attack-surface and application-testing platform. Surface Monitoring keeps a map of domains, subdomains, addresses, technologies and ports, and tests what it finds. Application Scanning is authenticated DAST, with crawling and payload fuzzing. API Scanning covers REST and GraphQL. Internal Scanning reaches systems off the public internet. PCI ASV scanning is an approved-scanning-vendor test for PCI DSS. The payloads come from Detectify's research and a crowdsourced community of hackers, including a large catalogue of subdomain-takeover checks. They say most of what they report never receives a CVE. That is their research claim. Hackly has not reproduced it.
If the job is the public internet, watched continuously, with payloads written by people who look for bugs, Detectify is the specialist. Hackly is not in that race. Subdomain discovery here is a wordlist, certificate-transparency logs and takeover fingerprints. Nothing watches for a new hostname appearing tomorrow, and nothing fuzzes an API the way a dedicated engine does. A penetration test is one authorized run, with one login, then a cooldown. Detectify's application scanning is built to keep going, authenticated, across the surface.
Detectify does not take the repository half of this app. There is no Semgrep, Trivy, Gitleaks or Composer audit in that product, and no Laravel-specific code checks. The service runs on their side. Hackly's PCI DSS page is one of the evidence packs in Compliance. It is not an ASV scan, and a bank that asked for an ASV will not accept it. Use Detectify, or another ASV, when that is the requirement.
The two can sit side by side. Detectify on the public surface you want watched every day. Hackly on the GitHub repository, and on the hosts where you can prove DNS and you want the raw Nuclei, ZAP and Composer output on your own disk. Intruder and Probely sit in the same hosted-external neighbourhood: someone else's payloads, no Laravel SAST.
Choosing between Aikido and Detectify is a separate question. Aikido is the wide platform, from the pull request to the cloud account. Detectify is the external-testing specialist. Hackly is the small self-hosted one. A team that already pays for one of the first two can still want the property Hackly actually has: the scanner output, and the GitHub token, stay on a machine they run.
The rest of the neighbourhood
| Tool |
What it is for |
Ahead of Hackly |
Where Hackly still fits |
| Aikido |
Hosted platform from code through cloud to runtime. Free tier, paid modules, on-prem on higher plans. |
Many languages, autofix pull requests, cloud and containers, authenticated DAST, API fuzzing, CI. |
Your own server, a hard DNS ownership gate, a Laravel and PHP stack you can read. |
| Detectify |
Hosted continuous attack surface, authenticated DAST, API fuzzing, PCI ASV. |
Asset discovery, payload research, subdomain takeover, testing that keeps running. |
Repository scanning, a self-hosted history, scans that cannot start without DNS proof. |
| Snyk |
Commercial developer security: dependencies, SAST, containers, IaC, licenses, fix pull requests. |
Remediation across package ecosystems, and a workflow developers already live in. Analysis runs at Snyk unless you put a broker in front. |
Ports, TLS, mail and ZAP against a host you verified. Snyk is a poor substitute for that. Hackly is a poor substitute for Snyk's fix pull requests. |
| Semgrep |
SAST, secrets and supply chain, with custom rules and pull-request comments. |
The rules, the assistant, and languages other than PHP. Hackly shells out to the Semgrep CLI. |
Semgrep is one task inside a scan that also runs ZAP, Nuclei and Composer. Pointing HACKLY_SEMGREP_CONFIG at another ruleset does not turn this app into Semgrep's platform. |
| GitHub Advanced Security |
CodeQL, Dependabot, secret scanning and push protection, on the pull request. |
The merge gate, with no extra server to run. It does not probe the deployed host or store a DNS ownership check. |
The host you operate. Hackly never posts a check on the pull request. Using both is a coherent setup: GitHub on the merge, Hackly on the domain. |
| SonarQube |
Quality gate and security hotspots. A self-hosted edition exists. |
Blocking a build, code quality, and broader language support. |
Anything about the running site: ports, mail, TLS, Nuclei, ZAP. SonarQube has none of that picture. |
| DefectDojo |
Open-source system of record. Imports from many scanners, with engagements, SLAs and client reports. |
Tracking a pentest program over years. It will not verify DNS and then run this pipeline. |
The scan itself. Triage here is four states and a note, which is enough for one team and thin for a consultancy. |
What this page will not pretend
- Hackly orchestrates nmap, Nuclei, ZAP, Nikto, WPScan, Semgrep, Trivy, Gitleaks, Checkov and Composer. Those projects own the detection. Hackly owns the queue, the history, triage that survives the next scan, the link between a domain and a repository, and the DNS gate.
- A one-off Nuclei run does not need this application installed.
- A human penetration test, a GRC suite, a WAF and a cloud-posture product remain separate purchases. The compliance score is not any of those.
- SAST here is PHP-shaped. Semgrep starts from
p/php. Checkpoint and the Laravel checks assume a Laravel tree. Other stacks are second-class, and saying otherwise would be advertising.
Requirements
- PHP 8.3+
- Composer
- SQLite (default) or MySQL / PostgreSQL
git (for repository clones)
- Node.js (optional — only for the Vite dev server; panel styles are plain CSS)
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, and a longer pass in a penetration test |
| nikto |
Web-server checks in a penetration test. The task is skipped when the binary is missing |
| wpscan |
WordPress version, plugins, themes and known vulnerabilities. No password guessing. Skipped when missing. HACKLY_WPSCAN_API_TOKEN is optional and only loads vulnerability data |
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, composer outdated |
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
- Targets → Add target — enter a domain (a pasted URL is normalized)
- Publish the TXT record shown on the target page at your DNS provider
- Check DNS — once the token is found the target is unlocked
- Start scan — pick a profile. Also scan linked repositories is already on when a repository is linked
- Follow progress live on the scan page; triage its findings or export a PDF / Markdown report
- The target page groups Findings, Scans and Linked repositories
The DNS dialog shows the scanner IP. Allowlist it if the host drops unknown clients.
Set HACKLY_EGRESS_IP when the worker cannot detect its own address.
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
- Settings → GitHub tokens → Add token (or create one inline while adding a repository)
- Repositories → Add repository —
owner/repo or a github.com URL, checked against the GitHub API
- Link the Targets where this code is deployed. The same link appears in compliance as one line, and a penetration test scans that code with the host
- Scan now — pick a profile. Also deep-scan linked targets is already on when a linked target is verified (DAST plus live Laravel probes)
- Review findings and scan history on the repository page; export reports from any scan
Repository findings belong to the repository, not to a target, so a repository can be scanned without any linked domain.
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
Linked apps
A link says “this repository is what runs on this domain”. Create it from
Linked repositories on the target, Linked targets on the repository,
or Linked repositories on a penetration test. The API uses
PUT /api/v1/targets/{id}/repositories and
PUT /api/v1/repositories/{id}/targets.
repository_ids on a penetration test writes the same link.
| Where | What the link changes |
| Target scan |
Also scan linked repositories is on by default. The CLI flag is --include-repos |
| Repository scan |
Also deep-scan linked targets is on when a linked target is verified. The CLI flag is --include-targets |
| Penetration test |
Always queues a deep scan of each linked, active repository. That scan does not start a second DAST of the same host |
| Routes |
A repository scan remembers Laravel routes, Next.js app/ pages and pages/ routes. The next DAST and the next penetration test request them on the authorized host. Ordinary pages are not findings |
| Compliance |
A linked pair is one line, shop.example.com + acme/shop. An unlinked target or repository stays on its own line |
Routes are stored when the repository scan finishes. The penetration test started in the same minute
still uses routes from the previous scan. The deep code scan queued beside it is immediate.
Penetration tests
Attack surface → Penetration tests is an engagement, not a profile on Start scan.
The target must already be DNS-verified and active. That standing check is the authorization:
start the test without publishing another TXT record, and without asking anyone to change DNS for this run.
A fresh hackly-pentest= record is only required when the target is not already verified.
That extra token lasts HACKLY_PENTEST_AUTH_MINUTES (30) and is consumed when the test starts.
There is a cooldown of HACKLY_PENTEST_COOLDOWN_HOURS (6) per target.
The authorization screen shows the scanner IP (the same address as the ordinary DNS check).
Allowlist it. Set HACKLY_EGRESS_IP if this server should not look the address up itself.
What you can declare
- Technology: Laravel, PHP, WordPress, Drupal, Django, Rails, Spring, ASP.NET, Node, Next.js, or mixed
- Swagger / OpenAPI URL, login areas, other URLs, and extra ports. Every URL must stay on the authorized host
- One test account. Hackly posts that form once and reviews the session cookie. It does not guess passwords
- Notes, how authentication works, and what is out of scope
What always runs
The declared URLs are extra. The run still covers the whole origin: tech fingerprint, path discovery,
TLS, technology surface, the OpenAPI document, the login pages, a fixed professional port set plus
the ports you named, Nuclei across Laravel, PHP, frontends, Apache, Nginx and WordPress, WPScan,
Nikto, and a longer OWASP ZAP pass. Missing wpscan or nikto skips that task.
WPScan is forced only when the technology is WordPress, and it never receives a password list.
API: create the engagement, then POST …/run when the target is already DNS-verified.
POST …/authorization and POST …/authorization/check are the fallback
for a target that has not passed that check. The payload includes scanner_ip.
Compliance
Security → Compliance scores evidence from scans you already ran.
It is not a certification, an attestation, or a legal opinion. A control Hackly cannot observe
is out of scope, not a pass.
The posture score starts at 100. Open highs, acknowledged highs, open mediums and lows,
unverified targets, and a workspace with assets but no completed scan in 30 days each reduce it.
The method is printed on the page and in the report.
Scope
All is the whole workspace. Every other line is one subject:
- a target with no repository, shown as the domain
- a repository with no target, shown as
owner/repo
- a linked pair, shown as
shop.example.com + acme/shop
The grade, the framework cards and the PDF / Markdown downloads follow that line.
A pair includes findings on the domain and findings on that repository.
Packs
DORA, SOC 2, NIS2, ISO/IEC 27001, PCI DSS, GDPR, NIST CSF 2.0, OWASP Top 10,
CIS Controls v8, HIPAA, Cyber Essentials, and the Essential Eight.
Each control is met, partial, a gap, not assessed, or out of scope.
API ids: dora, soc2, nis2, iso-27001,
pci-dss, gdpr, nist-csf, owasp-top-10,
cis-v8, hipaa, cyber-essentials, essential-eight.
API query subject on /api/v1/posture, /api/v1/compliance
and the report routes. Omit it for everything. Otherwise
target:{uuid}, repository:{uuid}, or
pair:{targetUuid}:{repositoryUuid}. An unknown or unlinked pair is 404.
Findings & triage
Findings collects every issue from target and repository scans, ranked High → Low.
Passed checks are recorded too but hidden unless you enable the Passed checks filter.
Click a row to open it in a slide-over and triage it, or select many rows and triage them in bulk.
| Status | Meaning |
| Open | Reported and not triaged yet |
| Acknowledged | Someone owns it — still listed under “Needs attention” on the dashboard |
| Fixed | Marked by you; target findings are also closed automatically when a later scan of the same checks no longer reports them |
| False positive | Dismissed, with a required note that stays on the finding. Re-detection keeps it dismissed |
A Fixed finding that is detected again is reopened automatically, so regressions surface on their own.
Each scan also keeps a snapshot of its own High / Medium / Low counts, so history stays accurate after re-scans.
On the finding, Copy Markdown puts every detail on the clipboard (severity, subject, evidence, and the false-positive note).
Copy AI prompt wraps that same write-up in a request for a root cause, a fix, and how to verify it.
Share link publishes a read-only page at /f/ plus 40 random characters. Unshare and the old address stops working.
The public page can also be fetched as ?format=markdown or ?format=prompt.
Weekly digest
Every Sunday at 08:00 (timezone from HACKLY_DIGEST_TIMEZONE, otherwise the app timezone)
Hackly emails every user a digest of the last seven days: posture grade, open findings by severity,
what is new, what was fixed or dismissed (with the false-positive note), failed scans, and the situations
around the workspace — unverified targets, paused assets, and anything without a completed scan in 30 days.
The same snapshot is in the panel notification bell and at GET /api/v1/digest.
The scheduler has to be running. Preview without sending mail:
php artisan hackly:digest --dry-run
API
Personal access tokens live on Profile. Each user sees only their own tokens.
Send Authorization: Bearer hk_…. Abilities are * (full, can mint tokens),
write (changes, and reads), and read.
The interactive reference is the API docs,
and a running app serves the same spec at /api/docs.
Compliance and posture accept subject. Penetration tests are their own
resources: create, authorize, check the TXT, then run. scanner_ip is the address to allowlist.
Issue a token from the shell when you do not want to open the panel:
php artisan hackly:token you@example.com --ability=read
Independent scan modes
| Goal |
UI |
CLI |
| Target only |
Start scan (toggle off) |
hackly:scan example.com |
| Target + repos |
Also scan linked repositories (on when linked) |
hackly:scan … --include-repos |
| Repo only |
Scan now (toggle off) |
hackly:repo-scan owner/repo |
| Repo + targets |
Also deep-scan linked targets (on when verified) |
hackly:repo-scan … --include-targets |
| Penetration test |
Penetration tests. Start when the target is already verified |
POST /api/v1/penetration-tests/{id}/run |
Laravel live pentest runs only when the repo scan includes linked targets.
Repo-only scans skip it. A penetration test’s linked repository scan is a deep code scan
and does not start that live probe again.
Scan profiles
Target (DAST)
| Profile |
Tasks |
| quick |
dns_info, mail_security, tls_check, port_scan, tech_fingerprint |
| standard |
+ subdomain_enum, origin_exposure, 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, checkpoint |
| standard |
+ semgrep_sast, trivy_sca, composer_outdated, laravel_live_pentest* |
| deep |
+ checkov_iac |
* Live pentest only when linked targets are included. checkpoint runs on every
repository profile and records each of the 30 Checkpoint checks.
Findings are normalized to LOW / MEDIUM / HIGH.
Penetration test
Not listed on Start scan. Tasks: tech_fingerprint, path_discovery,
tls_check, pentest_surface, pentest_api,
pentest_login, pentest_ports, nuclei_pentest,
wpscan, nikto, zap_pentest, plus a deep repository
scan for each linked repository.
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 |
| hackly:token {email} |
Issue a personal API token (--ability=full|read|write). Shown once |
| hackly:digest |
Send the weekly findings digest now (--dry-run to preview) |
| hackly:update-databases |
Refresh Nuclei templates, the Trivy database, and Semgrep rules. Scheduled daily at 03:30 |
| queue:work |
Process scan jobs |
| schedule:work |
Hourly output cleanup, the Sunday digest, and the daily Nuclei / Trivy / Semgrep refresh (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
HACKLY_NIKTO=nikto
HACKLY_WPSCAN=wpscan
# HACKLY_WPSCAN_API_TOKEN=
# HACKLY_EGRESS_IP=
# HACKLY_PENTEST_AUTH_MINUTES=30
# HACKLY_PENTEST_COOLDOWN_HOURS=6
# 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). The scanner IP is shown on that screen
- Penetration tests use that standing check. A second TXT is only required when the target is not already verified, and that token expires and is consumed on start. Declared URLs cannot leave the authorized host
- Repository scans require a GitHub token with read access to that repo
- Linking targets and repositories is optional. Linked scans stay on those assets. WPScan does not guess passwords
- 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
- API tokens are personal. A public finding link is off until you share it, and it does not reveal who published it
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
- Set
HACKLY_EGRESS_IP to the address operators should allowlist
- Keep the scheduler running so the Sunday digest is sent and
hackly:update-databases refreshes Nuclei, Trivy, and Semgrep data daily
- Run a persistent
queue:work, and schedule:work or cron schedule:run
Source and issues:
github.com/andreapollastri/hackly