skills: fleet map, default-deny standard, modded-app and vuln-scan skills

- server-fleet-map: add gringo row
- bastille-jail-provisioning: elevate default-deny-outbound to the standard
  for every new jail; reference gringo's live pf.conf
- modded-app-update-pattern: new skill, fork/rebase pattern for locally
  patched apps; uses tailnetatlas on gringo as the concrete example
- dependency-vuln-scanning: new skill, npm audit / pip-audit / osv-scanner
  cadence for deployments and monthly thereafter
This commit is contained in:
2026-08-08 14:01:43 +02:00
parent be36bcc802
commit fa7c367420
4 changed files with 210 additions and 0 deletions
@@ -130,6 +130,33 @@ curl -H 'Host: <domain>' http://<jail-ip>/
Only switch to testing via the host's `127.0.0.1` (or the public domain)
once the `sites-enabled` vhost for this specific site actually exists.
## Default-deny outbound is the standard for every new jail
**This is no longer an incident workaround — it is the default posture for
any jail created on any Bastille host in this fleet.** Most jails are
reverse-proxied inbound-only services and do not need outbound internet
access. Denying it by default limits blast radius if a jail is compromised
(fewer callback/exfil paths, no unexpected update pings).
When you finish provisioning a new jail, it must land in the `block out`
ruleset, not the `pass out` ruleset. Only add it to the allowed list if its
actual function genuinely requires external reach, and document the reason
in `pf.conf` next to the IP:
```
# Jails that need real outbound internet access (function confirmed):
table <outbound_allowed> { 10.20.0.14, 10.20.0.22, ... }
nat on $ext_if from <outbound_allowed> to any -> ($ext_if)
pass out quick on $ext_if from <outbound_allowed> to any keep state
block out quick on $ext_if from 10.20.0.0/24 to any
```
The live reference implementation is gringo's `/etc/pf.conf` (tracked in
`infra/gringo/pf/pf.conf`). Before considering any new jail complete, verify
its IP is either in the deny block or explicitly listed with a comment
explaining why it needs outbound access. "Might need it later" is not a
reason.
## Every jail needs a dedicated NPM-facing port, not just `listen 80`
**Confirmed real gap, 2026-08-07**: on jail hosts fronted by a shared
+82
View File
@@ -0,0 +1,82 @@
---
name: dependency-vuln-scanning
description: Run dependency vulnerability scans on every new deployment/migration and monthly thereafter. Covers Node, Python, and general OS/library scanning.
---
# Dependency Vulnerability Scanning
Dependency vulnerabilities are not a one-time checklist item. They should be
scanned on every new deployment or migration into the fleet, and then re-scanned
periodically thereafter. For this fleet size, **monthly** is a reasonable
cadence.
## When to run
- Before marking a new jail/container/app as "live" after provisioning or
migration.
- Before merging a dependency update.
- Monthly, against every actively maintained app and host.
This applies to Docker-host deployments too — a container image is just another
artifact with dependencies. Cross-reference the provisioning checklist in
`skills/bastille-jail-provisioning/SKILL.md` and add a vulnerability-scan step
before considering any new jail complete.
## Tooling by stack
### Node.js / npm
```bash
npm audit
```
Use `npm audit --audit-level=moderate` to filter noise if the project is large.
For a CI-friendly exit code, `npm audit --audit-level=high` will fail only on
high/critical findings. Always review the full output at least once.
If the project uses `pnpm` or `yarn`, use their equivalents (`pnpm audit`,
`yarn audit`). The Node ecosystem is common on this fleet — expect to use this
one often.
### Python
```bash
pip-audit
```
Run inside the project's virtualenv so it sees the same packages the app will
actually use. If `pip-audit` is not installed, add it to the dev tooling in the
virtualenv rather than the system Python.
### General / OS / multi-ecosystem fallback
```bash
osv-scanner -r /path/to/project
```
`osv-scanner` is the best general fallback: it understands lockfiles from npm,
PyPI, Go, Rust, Maven, and others, and queries the OSV database. Use it when a
project mixes ecosystems or when you want a single scanner across a whole host.
## What to do with findings
1. **Triage, do not blindly upgrade.** A CVE in a dev-only dependency is not the
same as one in the runtime path of a public-facing service.
2. **Patch or pin.** Prefer a real upstream upgrade. If the upstream fix is not
available yet and the risk is real, consider pinning, removing the dependency,
or a temporary local patch (tracked via `skills/modded-app-update-pattern`).
3. **Re-scan after the fix.** A green `npm audit` or `pip-audit` output is the
only proof the finding is resolved.
4. **Record exceptions.** If a finding is accepted (e.g. internal-only tool,
no reachable attack path), write the justification down in the deployment
notes or the issue tracker, not just in someone's memory.
## What not to do
- Do not treat vulnerability scanning as optional for "internal-only" tools.
Internal tools often have the same dependencies and the same CVEs.
- Do not run a scan and ignore the output because it is long. Parse it, triage
it, file issues for anything that needs follow-up.
- Do not rely solely on the language-specific scanner when the project also
pulls in OS packages or container base-image packages; add `osv-scanner` or
the image scanner as a second opinion.
+100
View File
@@ -0,0 +1,100 @@
---
name: modded-app-update-pattern
description: How to keep a vendor app that has been locally patched update-safe, using a fork or two-branch local repo so upstream updates can be merged/rebased instead of becoming a forgotten, untracked diff.
---
# Modded App Update Pattern
When an app or library has been custom-patched to work in this fleet, future
upstream updates must not silently overwrite those patches, and the patches
themselves must not become an undocumented, forgotten diff. The safe pattern is
to keep two distinct lines of history:
- `upstream-vendor` — tracks exactly what was shipped by the vendor, with no
local changes.
- `main` / `deployed` — the running version, with local patches as clean
commits on top of `upstream-vendor`.
This lets you merge or rebase the next upstream release onto `main` deliberately,
reviewing each local patch for conflicts and relevance rather than re-applying a
mystery diff by hand.
## When to use this
Use this whenever you modify source code, config templates, or package files
inside an app that came from upstream (GitHub, npm, PyPI, a vendor tarball).
Pure runtime secrets or host-specific `.env` files do not count — keep those
outside the repo entirely. But changes like these do:
- Patching `requirements.txt` because the upstream pins break on FreeBSD.
- Adding a local `run.sh` or rc.d script that the vendor does not provide.
- Modifying source to fix a FreeBSD path, disable a broken widget, or adapt an
API endpoint.
## Local-only version (no public fork)
If the repo is already cloned on the deployment host and you do not need a
public fork:
```bash
cd /app
git checkout -b upstream-vendor <upstream-tag-or-commit>
git branch -m main deployed # or keep main
git checkout deployed
git rebase upstream-vendor # apply local commits on top of clean vendor state
```
After that:
1. Fetch the new upstream tag into `upstream-vendor` and reset that branch to
it: `git checkout upstream-vendor && git reset --hard v2.3.4`.
2. `git checkout deployed && git rebase upstream-vendor`.
3. Resolve conflicts one patch at a time; each local change stays a separate
commit with a clear message.
4. Deploy and test.
## Public-fork version
If multiple hosts or people will deploy the app, create a fork on the internal
Gitea instance:
1. Fork `vendor/app` to `CloudHost/app-deployed`.
2. Add the original repo as an additional remote in your local clone:
`git remote add vendor https://github.com/vendor/app.git`.
3. Keep `upstream-vendor` tracking the vendor remote and `main`/`deployed`
tracking the fork remote.
4. Push local patches to the fork.
This makes the running version auditable and reusable without relying on one
host's working copy.
## Concrete fleet example: tailnetatlas on gringo
The `tailnetatlas` jail runs `crazyhoesl/tailnet-atlas`, cloned into `/app`.
The deployment has real local changes:
- `app/requirements.txt` was modified for this environment.
- `run.sh` was added because the project ships no rc.d-friendly entrypoint.
- `venv/` exists as an untracked build artifact.
If the upstream repo releases a new version, a plain `git pull` would either
merge the local changes blindly or fail with conflicts in `requirements.txt`.
The safer state is:
```
upstream-vendor -> commit matching the upstream release that was deployed
main/deployed -> upstream-vendor + "adapt requirements.txt for FreeBSD" +
"add run.sh for rc.d" + "ignore venv/"
```
Updating then becomes `git rebase upstream-vendor` on `main`, not a manual diff
rescue.
## What not to do
- Do not leave local changes uncommitted in a clone and hope you remember them
before the next update.
- Do not `git pull` upstream into the same branch that carries local patches
without reviewing the merge.
- Do not commit secrets into either branch. Runtime secrets belong in root-only
`.env` files or the secret store, never in the app repo.
+1
View File
@@ -16,6 +16,7 @@ whichever host you happen to already be connected to.
| **venus** | `100.109.251.127` (LAN `192.168.0.33`) | Corporate — internal business sites/tools, the iWP.es plugin-subscription platform, and other CloudHost-org first-party projects | Bastille jails, combined web+db (local MariaDB, no shared external DB host). New jails typically created via `bastille clone -a <known-good-base>` rather than from scratch. |
| **granja** | `100.98.197.28` | Affiliate network — the travel/tourism site network (menorca.ro, palma.ro, and similar) | No public interface configured directly; sits behind a reverse-proxy manager (NPM) over Tailscale. See `docs/server-granja.md` for the local-curl-simulates-NPM testing trick. |
| **funky** | `100.127.21.100` (LAN `192.168.0.218`) | Customers — sites migrated from the old Linux affiliate fleet, actual paying-customer properties (e.g. news.easycut.es, photomouse.ro) | Combined web+db+valkey, bastille jails. Migration target that absorbed several now-decommissioned Linux hosts (maagar, shoe, proton). |
| **gringo** | `100.95.46.79` (LAN `192.168.168.68`) | Internal apps/tools host — replacement for the old Linux Docker host barky-1 | ~45 Bastille VNET jails on `10.20.0.0/24` (APIs, dashboards, Gitea, analytics, productivity tools). Default-deny outbound pf model; NPM dedicated ports `8000 + last octet`. See `docs/server-gringo.md`. |
| **zamolxis** | `100.115.128.41` | Standalone (non-jailed) FreeBSD site host | Not part of the granja/external-fleet jail families — a single site (Newspaper/tagDiv theme, PHP 8.5) running directly on the host, not in a jail. |
| **cabrera** | `100.100.108.19`, SSH port `79` | External Linux affiliate-network host (BTPanel-style) | Part of the older Linux fleet (alongside raptor/maagar/formentor/moonie/spunky/proton/shoe — several since decommissioned/migrated to funky). Hosts easycut.es's main WooCommerce e-commerce site directly (not jailed). |