Harden public showcase privacy controls
Some checks failed
public-safety / privacy-and-secret-scan (push) Has been cancelled
Some checks failed
public-safety / privacy-and-secret-scan (push) Has been cancelled
This commit is contained in:
7
.github/workflows/public-safety.yml
vendored
7
.github/workflows/public-safety.yml
vendored
@@ -11,14 +11,17 @@ jobs:
|
|||||||
privacy-and-secret-scan:
|
privacy-and-secret-scan:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Check public-safe content
|
- name: Check public-safe content
|
||||||
run: python3 scripts/check-public-safety.py
|
run: python3 scripts/check-public-safety.py
|
||||||
|
|
||||||
|
- name: Compile detector source
|
||||||
|
run: python3 -m py_compile scripts/check-public-safety.py
|
||||||
|
|
||||||
- name: Scan Git history for secrets
|
- name: Scan Git history for secrets
|
||||||
uses: gitleaks/gitleaks-action@v2
|
uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2026 fizzlepoof
|
Copyright (c) 2026 Homelab Showcase contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ A public-safe overview of a real multi-host homelab built around segmented netwo
|
|||||||
| **GPU workstation** | CachyOS workstation | Operator workstation and opportunistic high-performance GPU compute |
|
| **GPU workstation** | CachyOS workstation | Operator workstation and opportunistic high-performance GPU compute |
|
||||||
| **Edge node** | Low-power Linux node | Weather dispatch and mesh-radio edge services |
|
| **Edge node** | Low-power Linux node | Weather dispatch and mesh-radio edge services |
|
||||||
|
|
||||||
See [Architecture](docs/architecture.md) for the data flow and [Services](docs/services.md) for the current service placement.
|
See [Architecture](docs/architecture.md) for the data flow and [Services](docs/services.md) for the representative service design by role.
|
||||||
|
|
||||||
## Design highlights
|
## Design highlights
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ docs/
|
|||||||
networking.md Segmentation and access-control model
|
networking.md Segmentation and access-control model
|
||||||
operations.md Deployment, verification, backup, and rollback patterns
|
operations.md Deployment, verification, backup, and rollback patterns
|
||||||
security.md Public/private boundary and secret-handling rules
|
security.md Public/private boundary and secret-handling rules
|
||||||
services.md Current service placement by host
|
services.md Representative service design by host role
|
||||||
examples/
|
examples/
|
||||||
compose/ Sanitized Compose and environment examples
|
compose/ Sanitized Compose and environment examples
|
||||||
scripts/
|
scripts/
|
||||||
@@ -62,7 +62,7 @@ scripts/
|
|||||||
|
|
||||||
## Public-safety policy
|
## Public-safety policy
|
||||||
|
|
||||||
Every commit is checked for:
|
Every commit is checked with a repository-specific privacy scanner for:
|
||||||
|
|
||||||
- private or management addresses
|
- private or management addresses
|
||||||
- MAC addresses and device identifiers
|
- MAC addresses and device identifiers
|
||||||
@@ -70,7 +70,7 @@ Every commit is checked for:
|
|||||||
- private-key material, tokens, passwords, and authorization headers
|
- private-key material, tokens, passwords, and authorization headers
|
||||||
- real `.env` files and key-bearing file types
|
- real `.env` files and key-bearing file types
|
||||||
|
|
||||||
Gitleaks also scans the complete Git history in CI. See [Security](docs/security.md).
|
The detector source is executed and syntax-checked rather than matched against its own regex literals. Binary files are rejected for manual review. Gitleaks separately scans the complete Git history in CI. See [Security](docs/security.md).
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ This repository contains public-safe architecture documentation and sanitized ex
|
|||||||
If you find sensitive information in this repository:
|
If you find sensitive information in this repository:
|
||||||
|
|
||||||
1. Do not quote it in a public issue.
|
1. Do not quote it in a public issue.
|
||||||
2. Contact the repository owner through an already established private channel.
|
2. Use [GitHub private vulnerability reporting](https://github.com/fizzlepoof/homelab-showcase/security/advisories/new), or contact the repository owner through an already established private channel.
|
||||||
3. Include only the affected path and the type of exposure until the value has been revoked.
|
3. Include only the affected path and the type of exposure until the value has been revoked.
|
||||||
|
|
||||||
A confirmed credential exposure is handled by rotating the credential first, removing it from current files, rewriting affected Git history, and verifying a fresh clone.
|
A confirmed credential exposure is handled by rotating the credential first, removing it from current files, rewriting affected Git history, and verifying a fresh clone.
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ The edge node handles always-on weather and mesh-radio functions close to the at
|
|||||||
client → tunnel gateway → internal reverse proxy → SSO policy → application
|
client → tunnel gateway → internal reverse proxy → SSO policy → application
|
||||||
```
|
```
|
||||||
|
|
||||||
The tunnel endpoint is the only public routing layer. Application containers do not require direct inbound router exposure.
|
The tunnel endpoint is the primary public routing layer for HTTP applications. Application containers do not require direct inbound router exposure; narrowly scoped non-HTTP exceptions are documented and isolated separately.
|
||||||
|
|
||||||
### Internal application request
|
### Internal application request
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ Removing a value from the latest commit does not remove it from Git history.
|
|||||||
|
|
||||||
## Repository controls
|
## Repository controls
|
||||||
|
|
||||||
The public repository uses two checks:
|
The public repository uses layered checks:
|
||||||
|
|
||||||
- `scripts/check-public-safety.py` rejects infrastructure identifiers, private paths, key-like files, and common secret patterns.
|
- `scripts/check-public-safety.py` rejects infrastructure identifiers, private paths, key-like files, binary files, raw-export filenames, non-approved URL hosts, and common secret patterns. Its source is executed and syntax-checked rather than matched against its own detector literals.
|
||||||
- Gitleaks scans complete Git history in CI.
|
- Gitleaks scans complete Git history in CI.
|
||||||
|
|
||||||
The scanner is deliberately conservative. Placeholder examples should use unmistakable values such as:
|
The scanner is deliberately conservative. Placeholder examples should use unmistakable values such as:
|
||||||
@@ -64,4 +64,4 @@ https://service.example.net
|
|||||||
|
|
||||||
## Reporting
|
## Reporting
|
||||||
|
|
||||||
Do not open a public issue containing a suspected secret. Revoke it first and use a private contact channel for disclosure.
|
Do not open a public issue containing a suspected secret. Revoke it first, then use [GitHub private vulnerability reporting](https://github.com/fizzlepoof/homelab-showcase/security/advisories/new). The repository owner may also be contacted through an already established private channel.
|
||||||
|
|||||||
186
docs/services.md
186
docs/services.md
@@ -1,168 +1,88 @@
|
|||||||
# Current Service Placement
|
# Representative Service Architecture
|
||||||
|
|
||||||
This is a public-safe service inventory verified against the live hosts during the September 2026 refresh. Versions, ports, addresses, domains, device IDs, and internal paths are intentionally omitted.
|
This page is a deliberately generalized snapshot of the lab's service design, informed by read-only host, container, storage, and service inspection on 2026-09-08. It is not a complete production inventory. Exact products are included only when they illustrate a reusable pattern; precise placement, versions, counts, ports, addresses, domains, and runtime identifiers remain private.
|
||||||
|
|
||||||
## Primary application host
|
## Primary application host
|
||||||
|
|
||||||
### Core platform
|
The primary host concentrates the shared application plane:
|
||||||
|
|
||||||
- TrueNAS SCALE
|
- storage-aware container runtime
|
||||||
- Docker application runtime
|
- internal reverse proxy and authenticated tunnel connector
|
||||||
- shared PostgreSQL, MariaDB, and Redis
|
- identity and SSO
|
||||||
- Traefik internal reverse proxy
|
- shared PostgreSQL, MariaDB, and Redis data services
|
||||||
- Newt tunnel connector
|
- source hosting and container administration
|
||||||
- Tailscale private-overlay connector
|
- household productivity, document, photo, and media front ends
|
||||||
- Authentik identity and SSO
|
- monitoring, metrics, logs, dashboards, uptime checks, and notifications
|
||||||
- Authelia retained for limited legacy migration paths
|
- workflow automation
|
||||||
- Headscale and Headplane pilot control plane
|
- always-on local model serving, model routing, vector search, and metasearch
|
||||||
- Gitea source hosting
|
|
||||||
- Dockhand and Homepage administration interfaces
|
|
||||||
|
|
||||||
### AI and search
|
Representative technologies include TrueNAS SCALE, Docker, Traefik, Authentik, PostgreSQL, Redis, Prometheus, Grafana, Ollama, and LiteLLM.
|
||||||
|
|
||||||
- Ollama always-on model serving
|
Large datasets are mounted from the storage host rather than duplicated into container-local volumes.
|
||||||
- LiteLLM model gateway
|
|
||||||
- Open WebUI
|
|
||||||
- Qdrant vector database
|
|
||||||
- SearXNG metasearch
|
|
||||||
- Firecrawl API, browser worker, queue, and supporting data services
|
|
||||||
- a VPN-isolated search egress path
|
|
||||||
|
|
||||||
### Media and libraries
|
|
||||||
|
|
||||||
- Plex
|
|
||||||
- Tautulli
|
|
||||||
- Audiobookshelf
|
|
||||||
- Calibre Web Automated
|
|
||||||
- Seerr
|
|
||||||
- Immich application and machine-learning services
|
|
||||||
- RomM
|
|
||||||
- GameVault
|
|
||||||
- Dispatcharr
|
|
||||||
|
|
||||||
Large libraries remain on the storage host and are mounted by the application host.
|
|
||||||
|
|
||||||
### Productivity and household applications
|
|
||||||
|
|
||||||
- Paperless-ngx with document conversion and extraction sidecars
|
|
||||||
- Karakeep with browser and search sidecars
|
|
||||||
- n8n
|
|
||||||
- KitchenOwl
|
|
||||||
- Donetick and a dashboard bridge
|
|
||||||
- Shlink and its web client
|
|
||||||
- Qui
|
|
||||||
- Scholarsome
|
|
||||||
- Doris Barbell
|
|
||||||
- Kima Hub
|
|
||||||
|
|
||||||
### Monitoring and notification
|
|
||||||
|
|
||||||
- Grafana
|
|
||||||
- Prometheus
|
|
||||||
- Loki
|
|
||||||
- Promtail
|
|
||||||
- cAdvisor
|
|
||||||
- node exporter
|
|
||||||
- Netdata
|
|
||||||
- Uptime Kuma
|
|
||||||
- Gotify
|
|
||||||
|
|
||||||
### Radio visibility
|
|
||||||
|
|
||||||
- MeshMonitor
|
|
||||||
- local map-tile service
|
|
||||||
|
|
||||||
## Storage and ingestion host
|
## Storage and ingestion host
|
||||||
|
|
||||||
### Data plane
|
The storage host keeps capacity-heavy and data-local workloads together:
|
||||||
|
|
||||||
- ZFS-backed bulk storage
|
- ZFS-backed bulk datasets
|
||||||
- application-data and media datasets
|
- snapshots and backup targets
|
||||||
- snapshot and backup targets
|
- media acquisition, organization, and post-processing
|
||||||
- synchronized school/document storage
|
- file synchronization
|
||||||
|
- private remote-support rendezvous and relay
|
||||||
|
- a secondary DNS resolver
|
||||||
|
- selected CPU-only inference utilities
|
||||||
|
- host and container monitoring
|
||||||
|
|
||||||
### Media ingestion
|
Representative technologies include Unraid, ZFS, the common media-automation ecosystem, Syncthing, RustDesk, Technitium DNS, and Netdata.
|
||||||
|
|
||||||
- Sonarr, including a separate anime workflow
|
|
||||||
- Radarr
|
|
||||||
- Lidarr
|
|
||||||
- Readarr variants
|
|
||||||
- Prowlarr
|
|
||||||
- Bazarr
|
|
||||||
- qBittorrent through a VPN gateway
|
|
||||||
- qbit_manage
|
|
||||||
- Unpackerr
|
|
||||||
- Autobrr
|
|
||||||
- Notifiarr
|
|
||||||
- Shelfmark
|
|
||||||
|
|
||||||
### Supporting services
|
|
||||||
|
|
||||||
- Syncthing hub
|
|
||||||
- RustDesk rendezvous and relay
|
|
||||||
- CPU text reranker
|
|
||||||
- secondary Technitium DNS replica
|
|
||||||
- Netdata
|
|
||||||
- Hawser
|
|
||||||
- Newt connector
|
|
||||||
|
|
||||||
## Offline and resilience host
|
## Offline and resilience host
|
||||||
|
|
||||||
### Offline knowledge platform
|
The resilience host stays useful when the primary application host or WAN is unavailable:
|
||||||
|
|
||||||
- Project N.O.M.A.D. administration layer
|
- offline reference and educational libraries
|
||||||
- Kiwix
|
- local notes and browser utilities
|
||||||
- two Kolibri generations for retained content compatibility
|
- local model and vector services
|
||||||
- CyberChef
|
- household helper and agent services
|
||||||
- Flatnotes
|
- CPU speech-to-text
|
||||||
- MeshCore Web
|
- Home Assistant in a dedicated virtual machine
|
||||||
- local Ollama and Qdrant
|
- game-server management
|
||||||
|
- mesh-radio observation
|
||||||
|
- a secondary DNS resolver
|
||||||
|
- trusted local file intake
|
||||||
|
|
||||||
Project N.O.M.A.D.-managed containers are treated as an appliance layer and are not manually rebuilt by general maintenance automation.
|
Representative technologies include Ubuntu Server, KVM, Home Assistant OS, Kiwix, Kolibri, Ollama, Qdrant, and Docker.
|
||||||
|
|
||||||
### Local agents and household tools
|
Managed appliance workloads on this host are excluded from broad cleanup automation and maintained through their own control plane.
|
||||||
|
|
||||||
- Honcho API, deriver, PostgreSQL/Vector database, and Redis
|
|
||||||
- Doris Schoolhouse
|
|
||||||
- Doris Kitchen
|
|
||||||
- CPU Whisper service
|
|
||||||
- LocalSend trusted intake
|
|
||||||
- Hawser
|
|
||||||
- node exporter
|
|
||||||
|
|
||||||
### Resilience and hardware-adjacent services
|
|
||||||
|
|
||||||
- Home Assistant OS in a KVM virtual machine with a dedicated Zigbee radio
|
|
||||||
- secondary Technitium DNS replica
|
|
||||||
- MeshCore companion observer as a system service
|
|
||||||
- Pelican/Wings game-server management
|
|
||||||
- Newt tunnel connector
|
|
||||||
|
|
||||||
## GPU operator workstation
|
## GPU operator workstation
|
||||||
|
|
||||||
- CachyOS desktop
|
The workstation provides optional accelerated capacity and daily operator tooling:
|
||||||
- high-performance NVIDIA GPU compute
|
|
||||||
- optional heavy Ollama and speech workloads
|
- high-performance local model inference
|
||||||
- Docker runtime
|
- accelerated speech-to-text and other GPU jobs
|
||||||
- printing service
|
- container runtime
|
||||||
|
- printing
|
||||||
- private-overlay access
|
- private-overlay access
|
||||||
- file-synchronization client
|
- file synchronization
|
||||||
- 3D-printing and slicing applications
|
- 3D-printing and slicing applications
|
||||||
|
|
||||||
The workstation is intentionally opportunistic capacity. Background services must continue when it is asleep or offline.
|
The workstation is opportunistic capacity. Routine background services must continue when it sleeps or is offline.
|
||||||
|
|
||||||
## Low-power edge node
|
## Low-power edge node
|
||||||
|
|
||||||
|
A small independent Linux node handles hardware-adjacent, always-on functions:
|
||||||
|
|
||||||
- weather collection and dispatch
|
- weather collection and dispatch
|
||||||
- mesh-radio/repeater support
|
- mesh-radio and repeater support
|
||||||
- wired server-lane placement
|
- wired placement on a server-oriented network segment
|
||||||
- low-power always-on operation
|
- upstream reporting without joining the primary application failure domain
|
||||||
|
|
||||||
## Placement rules
|
## Placement rules
|
||||||
|
|
||||||
1. Shared applications and databases prefer the primary application host.
|
1. Shared applications and databases prefer the primary application host.
|
||||||
2. Large data and media ingestion stay close to bulk storage.
|
2. Large data and ingestion stay close to bulk storage.
|
||||||
3. Offline knowledge and resilience services remain independent of the primary host.
|
3. Offline knowledge and resilience services remain independent of the primary host.
|
||||||
4. Heavy GPU work may use the workstation, but routine automation cannot require it.
|
4. Heavy GPU work may use the workstation, but routine automation cannot require it.
|
||||||
5. Hardware-adjacent radio and weather services stay on low-power edge nodes.
|
5. Hardware-adjacent radio and weather services stay on low-power edge nodes.
|
||||||
6. DNS, file synchronization, and remote support span hosts to avoid a single failure domain.
|
6. DNS, file synchronization, and remote support span hosts to reduce single points of failure.
|
||||||
|
7. The complete service inventory, operational exceptions, and incident state remain in the private source-of-truth repository.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: postgres:17-alpine
|
image: postgres:17.11-alpine3.24@sha256:18cfe3ef5e6815560c98237d6216d1e5119702fb0f3894c8785dd58b8bbe5d73
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${DATABASE_NAME}
|
POSTGRES_DB: ${DATABASE_NAME:?set DATABASE_NAME}
|
||||||
POSTGRES_USER: ${DATABASE_USER}
|
POSTGRES_USER: ${DATABASE_USER:?set DATABASE_USER}
|
||||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
POSTGRES_PASSWORD: ${DATABASE_PASSWORD:?set DATABASE_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- database-data:/var/lib/postgresql/data
|
- database-data:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
@@ -17,13 +17,9 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
application:
|
application:
|
||||||
image: traefik/whoami:v1.11
|
image: traefik/whoami:v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
|
||||||
database:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
networks:
|
||||||
- data
|
|
||||||
- ingress
|
- ingress
|
||||||
expose:
|
expose:
|
||||||
- "80"
|
- "80"
|
||||||
@@ -34,7 +30,7 @@ networks:
|
|||||||
data:
|
data:
|
||||||
internal: true
|
internal: true
|
||||||
ingress:
|
ingress:
|
||||||
name: ${INGRESS_NETWORK}
|
name: ${INGRESS_NETWORK:?set INGRESS_NETWORK}
|
||||||
external: true
|
external: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ PRIVATE_MOUNT = re.compile(r"(?i)/mnt/(?!storage\b|media\b|backups\b|example\b)[
|
|||||||
PRIVATE_OPT = re.compile(r"(?i)/opt/(?!example\b|application\b)[a-z0-9._-]+\b")
|
PRIVATE_OPT = re.compile(r"(?i)/opt/(?!example\b|application\b)[a-z0-9._-]+\b")
|
||||||
PRIVATE_KEY = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----", re.I)
|
PRIVATE_KEY = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----", re.I)
|
||||||
EMAIL = re.compile(r"(?i)\b[A-Z0-9._%+-]+@([A-Z0-9.-]+\.[A-Z]{2,})\b")
|
EMAIL = re.compile(r"(?i)\b[A-Z0-9._%+-]+@([A-Z0-9.-]+\.[A-Z]{2,})\b")
|
||||||
|
URL_HOST = re.compile(r"(?i)https?://([a-z0-9.-]+)")
|
||||||
SECRET_ASSIGNMENT = re.compile(
|
SECRET_ASSIGNMENT = re.compile(
|
||||||
r"(?i)\b(?:api[_-]?(?:key|token)|access[_-]?token|auth[_-]?token|token|password|passwd|"
|
r"(?i)\b(?:api[_-]?(?:key|token)|access[_-]?token|auth[_-]?token|token|password|passwd|"
|
||||||
r"client[_-]?secret|cookie|private[_-]?key)\b\s*[:=]\s*[\"']?([^\s\"',}]+)"
|
r"client[_-]?secret|cookie|private[_-]?key)\b\s*[:=]\s*[\"']?([^\s\"',}]+)"
|
||||||
@@ -43,11 +44,14 @@ AUTHORIZATION = re.compile(
|
|||||||
OPAQUE = re.compile(r"\b[A-Za-z0-9_+/=-]{32,}\b")
|
OPAQUE = re.compile(r"\b[A-Za-z0-9_+/=-]{32,}\b")
|
||||||
|
|
||||||
RISKY_SUFFIXES = {
|
RISKY_SUFFIXES = {
|
||||||
".pem", ".key", ".p12", ".pfx", ".kdbx", ".ovpn", ".mobileconfig"
|
".pem", ".key", ".p12", ".pfx", ".kdbx", ".ovpn", ".mobileconfig",
|
||||||
|
".db", ".sqlite", ".sqlite3", ".dump", ".pcap", ".pcapng",
|
||||||
}
|
}
|
||||||
RISKY_NAMES = {
|
RISKY_NAMES = {
|
||||||
".env", "id_rsa", "id_ed25519", "id_ecdsa", "id_dsa", "wg0.conf"
|
".env", "id_rsa", "id_ed25519", "id_ecdsa", "id_dsa", "wg0.conf"
|
||||||
}
|
}
|
||||||
|
RAW_EXPORT_NAME = re.compile(r"(?i)(?:^|[-_.])(?:backup|dump|export|baseline|snapshot)(?:[-_.]|$)")
|
||||||
|
ALLOWED_URL_HOSTS = {"github.com", "service.example.net"}
|
||||||
|
|
||||||
|
|
||||||
def tracked_files() -> list[Path]:
|
def tracked_files() -> list[Path]:
|
||||||
@@ -88,6 +92,8 @@ def main() -> int:
|
|||||||
findings.add(f"{relative}: prohibited key/config suffix")
|
findings.add(f"{relative}: prohibited key/config suffix")
|
||||||
if re.fullmatch(r"wg\d+\.conf", name):
|
if re.fullmatch(r"wg\d+\.conf", name):
|
||||||
findings.add(f"{relative}: prohibited VPN configuration filename")
|
findings.add(f"{relative}: prohibited VPN configuration filename")
|
||||||
|
if RAW_EXPORT_NAME.search(name):
|
||||||
|
findings.add(f"{relative}: raw export/backup-style filename requires review")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = path.read_bytes()
|
data = path.read_bytes()
|
||||||
@@ -95,6 +101,7 @@ def main() -> int:
|
|||||||
findings.add(f"{relative}: unreadable: {exc}")
|
findings.add(f"{relative}: unreadable: {exc}")
|
||||||
continue
|
continue
|
||||||
if b"\0" in data[:4096]:
|
if b"\0" in data[:4096]:
|
||||||
|
findings.add(f"{relative}: binary tracked file requires manual review")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for line_number, line in enumerate(data.decode("utf-8", errors="ignore").splitlines(), 1):
|
for line_number, line in enumerate(data.decode("utf-8", errors="ignore").splitlines(), 1):
|
||||||
@@ -124,6 +131,13 @@ def main() -> int:
|
|||||||
if not domain.endswith(("example.com", "example.net", "example.org")):
|
if not domain.endswith(("example.com", "example.net", "example.org")):
|
||||||
findings.add(f"{prefix}: non-example email address")
|
findings.add(f"{prefix}: non-example email address")
|
||||||
|
|
||||||
|
for match in URL_HOST.finditer(line):
|
||||||
|
host = match.group(1).lower()
|
||||||
|
if host not in ALLOWED_URL_HOSTS and not host.endswith(
|
||||||
|
(".example.com", ".example.net", ".example.org")
|
||||||
|
):
|
||||||
|
findings.add(f"{prefix}: non-approved URL host")
|
||||||
|
|
||||||
for match in SECRET_ASSIGNMENT.finditer(line):
|
for match in SECRET_ASSIGNMENT.finditer(line):
|
||||||
value = match.group(1)
|
value = match.group(1)
|
||||||
if not is_placeholder(value):
|
if not is_placeholder(value):
|
||||||
@@ -134,12 +148,13 @@ def main() -> int:
|
|||||||
if not is_placeholder(value):
|
if not is_placeholder(value):
|
||||||
findings.add(f"{prefix}: authorization material")
|
findings.add(f"{prefix}: authorization material")
|
||||||
|
|
||||||
for candidate in OPAQUE.findall(line):
|
line_without_urls = re.sub(r"https?://\S+", "", line)
|
||||||
|
for candidate in OPAQUE.findall(line_without_urls):
|
||||||
if is_placeholder(candidate):
|
if is_placeholder(candidate):
|
||||||
continue
|
continue
|
||||||
# Commit hashes and content digests are still identifiers; require a label.
|
# Commit hashes and content digests are still identifiers; require a label.
|
||||||
labelled_hash = re.search(
|
labelled_hash = re.search(
|
||||||
r"(?i)\b(?:sha(?:1|256|512)|digest|commit|checksum|example[_-]?hash)\b",
|
r"(?i)(?:\b(?:sha(?:1|256|512)|digest|commit|checksum|example[_-]?hash)\b|\buses\s*:)",
|
||||||
line,
|
line,
|
||||||
)
|
)
|
||||||
if labelled_hash and re.fullmatch(r"[0-9a-fA-F]{32,128}", candidate):
|
if labelled_hash and re.fullmatch(r"[0-9a-fA-F]{32,128}", candidate):
|
||||||
@@ -154,7 +169,11 @@ def main() -> int:
|
|||||||
print(f"- {finding}")
|
print(f"- {finding}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"Public-safety scan passed: {len(files)} tracked files checked")
|
content_count = len(files) - int(any(path.relative_to(ROOT).as_posix() == SELF for path in files))
|
||||||
|
print(
|
||||||
|
"Public-safety scan passed: "
|
||||||
|
f"{content_count} tracked files content-scanned; detector source executed separately"
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user