Skip to content

Security and secrets runbook

DTED.org is a public-information terrain service. It is not authorized for Federal Contract Information, Controlled Unclassified Information, classified, export-controlled, proprietary, or operationally sensitive data. The CMMC-style notice on the public pages is a handling boundary, not a claim of CMMC certification.

Use a trusted operator shell. Never paste credential values into tickets, commits, command arguments, shell history, chat, screenshots, or logs.

Security boundaries

  • Nginx is the only public listener. The portal binds to 127.0.0.1:8780.
  • The portal and worker run as an unprivileged host UID/GID with a read-only root filesystem, all Linux capabilities dropped, no-new-privileges, a PID limit, and an isolated temporary filesystem.
  • Dataset releases are immutable. Public and restricted activation links are separate.
  • Preview hosts serve public-information release candidates by default and carry a noindex response policy. Restricted hosts deny clients outside the VPN allowlist.
  • The admin site uses TLS and central AndKit OIDC through the local OAuth2 proxy. Nginx forwards an HMAC-protected identity contract to the portal; DTED does not accept a local browser password.

Credential inventory and permissions

Expected root-managed files are:

File Owner and mode Purpose
/etc/andkit-auth/*.conf root:root 0644 (no credentials) Central forward-auth and OAuth2 locations
/srv/dted/.env deployment owner 0600 Portal configuration including the proxy identity secret
/etc/dted/usgs.env root:root 0600 USGS M2M account and application token
/etc/dted/namecom.env root:root 0600 DNS API account and token
/etc/dted/*.env for other providers root:root 0600 Source-specific credentials
/etc/dted/tls/privkey.pem target root-readable TLS private key
/etc/dted/qa_allowlist.conf root:root 0644 Non-secret public-by-default QA network policy
/etc/dted/vpn_allowlist.conf root:root 0644 Non-secret restricted-access CIDRs
/srv/dted-data/dted.sqlite3 data owner/group 0640 Portal jobs, uploads, and dataset registry

Audit names, owners, modes, and link targets without printing file contents:

sudo find /etc/dted -maxdepth 1 -type f \
  -printf '%M %u:%g %p\n' | sort
sudo find /etc/dted/tls -maxdepth 1 -type l \
  -printf '%p -> %l\n' | sort
sudo stat -Lc '%A %U:%G %n' /etc/dted/tls/privkey.pem

Create, disable, and assign operator roles through the central AndKit identity service. Rotate the edge-to-portal identity secret using the AndKit auth runbook, update DTED_PROXY_IDENTITY_SECRET, recreate the portal, and reload Nginx as one coordinated change. A direct request to the loopback portal without valid signed identity headers must continue to fail closed.

Rotate provider or DNS credentials in the provider console, atomically replace the root-only file, validate the minimum required operation, then revoke the old credential. Never print a credential to prove it works.

Repository and history secret scan

Run the pinned scanner with complete redaction. The small-target pass avoids spending time on ignored terrain binaries; scan large JSON receipts separately with the metadata-safe tool below.

cd /home/gba/Agents/DTED.org
GITLEAKS='zricethezav/gitleaks@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f'
docker pull "$GITLEAKS"
docker run --rm -v "$PWD:/repo:ro" "$GITLEAKS" dir /repo \
  --redact=100 --no-banner --max-target-megabytes 5 --max-archive-depth 0
docker run --rm -v "$PWD:/repo:ro" "$GITLEAKS" git /repo \
  --redact=100 --no-banner

The receipt scanner reports only JSON locations and finding categories, never values:

python3 tools/scan_secret_metadata.py \
  data/src \
  /srv/dted-sources \
  /srv/dted-data/source-manifests

A clean scan does not prove a credential was never disclosed through another channel. Rotate anything suspected of exposure.

Static, dependency, and image audit

Run application tests first:

make test
.venv/bin/mkdocs build --strict
git diff --check

Audit the exact built image's Python environment:

docker run --rm dted-portal python -m pip check
docker run --rm --entrypoint sh dted-portal -c \
  'python -m pip install --no-cache-dir pip-audit==2.10.1 >/dev/null &&
   pip-audit --skip-editable'

Scan OS and Python packages for fixed high or critical vulnerabilities:

TRIVY='aquasec/trivy@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f'
docker pull "$TRIVY"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  "$TRIVY" image --severity HIGH,CRITICAL --ignore-unfixed \
  --exit-code 1 dted-portal

Run Bandit at medium confidence and severity or higher. Any suppression must be narrow, next to the line, and explain why untrusted input cannot reach the flagged operation:

docker run --rm -v "$PWD:/src:ro" -w /src python:3.11-slim \
  sh -c 'pip install --quiet bandit==1.9.4 &&
         bandit -q -r dted_enclave tools -ll -ii'

Record scanner versions, image digests, scope, findings, remediation, and residual risks in a dated audit report. Do not record secrets or client IPs.

Deployment and external-surface checks

Deploy only a committed revision:

git status --short
git rev-parse HEAD
sudo tools/install_production.sh

Then verify:

cd /srv/dted
docker compose ps
docker inspect dted-portal-1 \
  --format 'readonly={{.HostConfig.ReadonlyRootfs}} caps={{json .HostConfig.CapDrop}} security={{json .HostConfig.SecurityOpt}} pids={{.HostConfig.PidsLimit}}'
curl -fsS http://127.0.0.1:8780/healthz
sudo nginx -t
ss -ltnp
curl -sSI https://docs.dted.org/
curl -sSI https://admin.dted.org/
curl -sSI https://preview-ca-3dep.dted.org/
curl -sSI https://not-a-dataset.dted.org/
openssl s_client -connect dted.org:443 -servername dted.org \
  -tls1_2 -verify_return_error </dev/null

Expected results are:

  • portal health is successful only through loopback;
  • admin redirects an unauthenticated browser to auth.andkit.io;
  • a staged preview is 200, carries X-Robots-Tag: noindex, nofollow, noarchive, and contains no sensitive data;
  • an unknown dataset is 404;
  • TLS verification succeeds;
  • HTTPS responses omit the Nginx version and include HSTS, nosniff, frame denial, a no-referrer policy, and restrictive browser permissions.

Use tools/smoke_test.py for every active state and candidate. The smoke test also verifies ZIP signatures, byte ranges, TAK client hostnames, the SDS index, and source isolation.

Backup and recovery

Protect and test backups of:

  • SQLite through its online backup API or with services stopped;
  • /srv/dted-data/releases, activation links, source quality, promotion records, and evidence;
  • /srv/dted-sources receipts, archives, and content-addressed cache;
  • /srv/dted-grids and its hash manifest;
  • Nginx configuration, QA/VPN network-policy files, and certificates;
  • root-managed secrets in a separate encrypted, access-controlled store.

Do not put secrets in the same broadly accessible archive as public terrain. Test restoration to an isolated host. Confirm database integrity, hashes, permissions, Nginx configuration, container hardening, public smoke tests, and restricted-dataset denial before returning service.

Incident response

  1. Preserve relevant timestamps, release IDs, image IDs, audit logs, and redacted request metadata. Do not copy secret values into the incident record.
  2. Contain the smallest affected boundary: close a preview window, remove a compromised CIDR, disable the affected provider credential, or stop Nginx if the public edge cannot be trusted.
  3. If a repository secret is suspected, rotate and revoke it before history rewriting. A force-push does not remove clones, caches, artifacts, or logs.
  4. Rebuild from a reviewed commit and pinned dependencies. Do not repair an immutable release in place.
  5. Validate scans, tests, TLS, permissions, health, and representative TAK clients before recovery.
  6. Record the cause, affected interval and assets, containment, rotations, verification evidence, and follow-up owner.

Report suspected security incidents to SNSTAC Mission Operations Support at info@snstac.com.