Gardience Documentation gardience.com →
Docs / Enterprise

Penetration test readiness

How an outside penetration tester will attack Gardience, what stops each attack, and which automated test proves it on every release. Testers work from public methods: the OWASP Web Security Testing Guide (WSTG) for web applications, the OWASP Application Security Verification Standard (ASVS) level 2 as the checklist behind it, the Penetration Testing Execution Standard (PTES) for the overall flow, and NIST SP 800-115 in government work. For a Salt-based product they also run the published Salt exploits. This page follows that order.

Everything in the "Proof" column runs in the release check against a fresh install of the exact bundle being released (tools/release-check.sh); the release cannot be signed if any of it fails. You can run the same tests against an install you own: VIEWER=user:pass tools/pentest/run.sh NAME HTTPS_PORT ADMIN PASSWORD.

This is our own testing. It is not an independent third-party penetration test or an attestation.

1. Reconnaissance and exposure (PTES intelligence gathering, WSTG-INFO)

What a tester triesWhat stops itProof
Port scan for every serviceOnly the console (443, 80 redirect) and the two Salt agent ports are published. salt-api, the Gardience service and the playbook runner are internal onlynmap in run.sh (top 1,000 ports plus the internal ones)
Version banners to look up known CVEsserver_tokens off; no X-Powered-By; Salt's web server error pages (which name CherryPy and its version) are replaced at the front door and in the sign-inwstg.py: 27 paths, headers and bodies
Forgotten files: .git, .env, backups, API docs, server-status, metricsEverything not explicitly routed returns 404; the API description is not servedwstg.py, nuclei (14,000+ templates)
Stack traces and debug pagesErrors return short JSON; Salt master errors are never passed throughwstg.py broken-body checks on every route

2. Configuration and transport (WSTG-CONF, WSTG-CRYP)

What a tester triesWhat stops itProof
Weak TLS, old protocols, known TLS attacksTLS 1.2 and 1.3 only, AEAD ciphers, forward secrecy, no session ticketstestssl.sh
Missing security headers, clickjackingHSTS (1 year), nosniff, frame-ancestors 'none' and X-Frame-Options DENY, no-referrer, strict permissions policy, cross-origin isolation, on every response including errorswstg.py header checks, ZAP baseline
Inline script injectionContent-Security-Policy with scripts only from the server or a per-response noncewstg.py, ZAP
TRACE, open CORSTRACE refused; Salt's CORS headers are stripped, so no other site may read any responsewstg.py (OPTIONS and GET with a foreign Origin on four paths)
Sensitive data cached by browsers or proxiesCache-Control: no-store on every console and API responsewstg.py

3. Identity and authentication (WSTG-IDNT, WSTG-ATHN, ASVS V2)

What a tester triesWhat stops itProof
Default or guessable accountsNo built-in passwords; the installer generates themwstg.py (8 common pairs)
Username enumeration from different errorsWrong password and unknown user get the same answerwstg.py
Brute force from one address10 sign-ins a minute per address at the front doorwstg.py
Password-only sign-in for privileged accountsOne-time codes from an authenticator app for local and directory accounts, required per role (gardience mfa require admin); single sign-on users get their identity provider's MFArelease gate (code, replay, required-but-missing), unit tests (RFC 6238 vectors)
Brute force spread across many addresses10 failed sign-ins in 15 minutes pause that account until the window passes (a throttle, so an attacker cannot lock someone out for good)wstg.py, unit test
Other Salt authentication back ends (auto, sharedsecret, rest)Only file, LDAP, PAM and Gardience SSO reach the masterwstg.py
Signing in as a service identityService names are reserved; the break-glass account exists only while root on the server has it openwstg.py, release gate
Injection in the sign-in body (NoSQL operators, SQL, extra Salt fields)Only well-formed username, password and eauth strings are forwardedwstg.py
SSO: forged, replayed or stolen responses, signature wrapping, XML attacksSigned assertions required, unsolicited responses refused, one-time state bound to the browser, hardened XML parsersso-attack.js in the lab suites

4. Sessions (WSTG-SESS, ASVS V3)

What a tester triesWhat stops itProof
Stealing the session cookieThe cookie is Secure, HttpOnly and SameSite=Strictwstg.py, unit test
Cross-site request forgery: another site makes the signed-in browser send commandsAny change sent to Salt's API must carry the console's token header, which a browser never adds to a request another site makes; the Gardience API accepts only the headerwstg.py (form, text and multipart posts with a foreign Origin)
Token in the URL, made-up tokensRefusedwstg.py
Reusing a token after sign-outDead everywhere at once (Gardience and Salt's API)wstg.py, release gate
Long-lived sessions8-hour token; directory and SSO sessions end after 1 hour by default; removing a user ends their sessions and API keys at oncerelease gate, unit tests

5. Access control (WSTG-ATHZ, ASVS V4)

What a tester triesWhat stops itProof
Calling every API route without signing in401 or 404 on every one of the ~100 routes; a console page without a session is only an empty loading shell; public on purpose: health checks, the CLI download and SSO entry pointswstg.py (enumerated from the running app)
A viewer calling every change routeRefused on every onewstg.py with a viewer account
An admin's console session running raw Salt (cmd.run, file writes, key changes, orchestration, ssh client)Admins are governed: changes go through Gardience (approvals, tickets, audit). Raw Salt rights exist only on the time-limited break-glass accountwstg.py (9 calls), release gate
Self-approving a changeTwo-person approval; the launcher cannot approverelease gate
A low-privilege user flooding the fleet with jobs (denial of service)Refreshes a viewer may start (fleet scan, inventory, drift check) run one at a time, however often they are calledwstg.py viewer checks
Path traversal and IDs in URLsRefused cleanly on every route with a path parameterwstg.py (5 encodings, every such route)

6. Input handling (WSTG-INPV, ASVS V5)

What a tester triesWhat stops itProof
Server-side request forgery through admin-set addresses (webhook, ticket check)Loopback, link-local, cloud metadata, multicast and the stack's own services are refused at save and at call time; the call goes to the address that was checked (no DNS rebinding); redirects are not followed. Office addresses (an on-premises Jira) still workwstg.py (8 targets x 2 settings), unit tests
Stored XSS from a compromised machine (it controls its own name and facts)Every page escapes machine data; the CSP stops any script that slipped throughui/xss.sh: plants markup in a live agent's facts, then checks 24 console pages in Chrome
Reflected XSS, open redirectsNot reflected; no redirect leaves the serverwstg.py
Wrong-shaped or broken JSON, deeply nested bodiesEvery body must be a JSON object, checked once for all routes: a clean 4xx, never a server errorwstg.py (6 bodies on every change route)
Oversized bodies413 at the front door above 20 MBwstg.py
Formula injection in exported reports (a machine named =HYPERLINK(...))Every exported text cell starting with = + - @ is prefixed with an apostropheunit test
Command injection through runbook inputsEach input is typed, checked against its pattern and passed to the Salt function as one whole argument; no shell string is built from it. The one runbook that runs a shell command ("Run a command") does so on purpose and needs command-execution rightsZAP active scan, unit tests

7. Salt-specific attacks

What a tester triesWhat stops itProof
CVE-2020-11651 (Metasploit saltstack_salt_unauth_rce, saltstack_salt_root_key): read the root key or publish jobs without a keySalt 3008.3; refusedsalt-cves.py (sends the exploit's requests to the live master)
CVE-2020-11652: wheel and runner calls without a keyRefusedsalt-cves.py
CVE-2021-25281/25282/25283 (salt-api wheel_async, ssh client)salt-api is not published; /run and /token are not routed; the ssh client is disabledrelease gate, nmap
A compromised agent forging job events or master eventsHardened master refuses agent-sent events on reserved tags and stamps the senderforge/run.sh in the lab suites
A compromised agent claiming a group to get its secretsSelf-reported group claims wait for admin approvalrelease gate

8. Business logic and audit (WSTG-BUSL, ASVS V7)

What a tester triesWhat stops itProof
Editing or truncating the audit logHash-chained (HMAC) log, verified on demand and after restorerelease gate
Using break-glass without a traceEvery opening and closing is audited and announced, even if it was open for seconds; its sign-in and every command are auditedrelease gate
Remote code execution in the web service, used to grant itself or an attacker more rightsThe web service mounts its configuration read-only; users, Salt permissions and break-glass are written only by a container with no network and by root's gardience commandrelease gate (service cannot write master.d, keeper has no network)
A compromised web service turned against the fleetThe automation identity runs only the functions Gardience calls (no ".*"); arbitrary command execution is a root-controlled switchunit tests; release gate (no ".*", commands off refuses Run a command)
An AI agent or leaked API key abusing the API or MCPPer-key rate limit, keys narrowed to runbooks and node groups, approval by default, hourly cap on unapproved runs, approve only with an admin-made key and never one's own, every read and write audited, 20-call batch capunit tests; wstg.py agent checks; release gate agent flow
An operator or approver calling admin-only routesAll 25 admin-only change routes refuse both roles (403)wstg.py with operator and approver accounts
Reading secrets through Salt's configuration readEvery secret (LDAP bind password and any other credential) is blanked in what the call returnsSalt image build check (harden.py)
Stolen backupsEncrypted by default (AES-256) with the server's backup key or your passphrase file; keyed integrity coderelease gate (encrypted by default; a new server cannot restore it without the key)
Tampered evidence for an auditorEvidence packs signed with the install's Ed25519 key; a verifier ships in the bundlerelease gate
Tampered or replayed upgrade bundlesRelease-key signatures; a bundle cannot replace the key it is checked againstrelease gate

What a tester may still report

Gardience by Bouziden Management LLC.