d62e6c2dbe
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Failing after 2m14s
Tessera CI/CD / Desktop-Pakete bauen (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped
Tessera CI/CD / Sicherheitspruefung (nur Bericht) (push) Has been skipped
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
232 lines
27 KiB
Markdown
232 lines
27 KiB
Markdown
# Quick 261009-p0m: Sicherheitsprotokoll + CI-Sicherheitspruefungen - Research
|
|
|
|
**Researched:** 2026-10-09 (HEAD `cc713b5`, clean tree)
|
|
**Domain:** Gitea Actions (act_runner) security scanning, OWASP ZAP baseline, security-audit inventory
|
|
**Confidence:** HIGH for runner facts, baseline numbers and the ZAP header mechanism (all measured this session); MEDIUM for artifact upload / cache persistence (see Assumptions)
|
|
|
|
<user_constraints>
|
|
## User Constraints (from task prompt, locked wishes of 08.10.)
|
|
1. Separate document `docs/sicherheitsprotokoll.md` (linked from `docs/README.md` and the guides): ALL security checks done so far + a baseline full scan, kept current. Audience: non-programmer owner and later customers -> plain German, "Sie".
|
|
2. Security step in `.gitea/workflows/ci.yml`: dependency audit, Semgrep, Trivy on built images, gitleaks incl. full history. REPORTING ONLY, must never fail the build.
|
|
3. OWASP ZAP baseline against alpha (`https://alpha.tessera.ctl.de`) before releases, scripted. Basic Auth in front of alpha must NOT be removed.
|
|
4. First complete run over all of Tessera = baseline in the protocol.
|
|
### Claude's Discretion: tool selection, versions, job placement, file layout.
|
|
### Deferred / out of scope: licensing topic, Mandantenfaehigkeit as open topic, any password-leak/rotation warnings, any Docker deploy on the test server (User does pull/up).
|
|
</user_constraints>
|
|
|
|
## Project Constraints (from CLAUDE.md + memory)
|
|
- Work only through GSD entry points; German, "Sie" in docs/app texts, "du" in chat; no technical choices put to the User.
|
|
- Never print secret values (gitleaks `--redact`); `gespraech-2026-11/` must never enter git or any scan output (it is untracked; all source scans below ran on a `git archive HEAD` copy, history scan reads `.git` only).
|
|
- Gitea is reached via `localhost:3002` (host) / host gateway :3002 (job containers), never via `git.vicolab.de`; NPM blocks large uploads. Push bundled, not per small change.
|
|
- Basic Auth in front of alpha stays (memory `project_alpha_basic_auth`).
|
|
|
|
## Summary
|
|
The runner is a single `gitea-runner` (act_runner v0.6.1) whose job containers are `gitea/runner-images:ubuntu-latest` (Ubuntu 24.04, python 3.12, pipx, jq, curl, docker CLI 29.5) on Docker network `gitea`, with the host `docker.sock` mounted and a persistent `act-toolcache` volume at `/opt/hostedtoolcache`. [VERIFIED: `docker inspect` of running job container GITEA-ACTIONS-TASK-1458 mounts, runner `.runner` labels `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest`, `docker run gitea/runner-images:ubuntu-latest` tool probe]. Because the socket is the HOST daemon, the images built by `publish` (tags `localhost:3002/schalli/tessera-ctl/{api,web}:{beta,latest}`) are already present locally after `publish-images.sh` and can be scanned without pulling. [VERIFIED: `docker images` on host shows exactly those tags]
|
|
|
|
All four scanners were downloaded and executed from inside the real runner image on network `gitea`: gitleaks 8.30.1, trivy 0.75.0, osv-scanner 2.6.0 binaries (SHA256 checked against the release checksum files) and `pipx install semgrep==1.180.0` (27 s) all work. [VERIFIED: probe script run in `gitea/runner-images:ubuntu-latest --network gitea`] So the CI step needs no extra images and no bind mounts.
|
|
|
|
The baseline is NOT clean on dependencies: the lockfile has 151 production vulnerabilities (5 critical, 73 high), dominated by a handful of packages that have patch-level fixes (Next.js 15.5.19 -> >=15.5.27 first). Secrets are clean (20 gitleaks hits, all test fixtures/doc snippets). Source findings are almost all false positives or accepted design (one real hardening item: AES-GCM auth-tag length). ZAP needs no header trick today: the dev host and containers on the `gitea` network get HTTP 200 on `/login` of alpha (no 401); a verified fallback via ZAP hook exists.
|
|
|
|
**Primary recommendation:** Add ONE job `security` to `ci.yml` with `needs: publish`, `continue-on-error: true`, every step ending `|| true`; install pinned binaries by curl+sha256; Trivy cache in `/opt/hostedtoolcache`; reports -> log summary lines + best-effort `upload-artifact@v3`; ZAP as a local script `scripts/security/zap-baseline-alpha.sh` (not in CI, because alpha is internal-only reachable).
|
|
|
|
## Architectural Responsibility Map
|
|
| Capability | Primary Tier | Secondary | Rationale |
|
|
|---|---|---|---|
|
|
| Dependency/secret/SAST scans | CI runner (job container) | dev host (manual re-run) | scanners read repo + lockfile; no runtime service involved |
|
|
| Image scan | CI runner via host docker daemon | — | images exist only in the host daemon after `publish` |
|
|
| ZAP baseline | Dev host (docker) | — | alpha is reachable internally only; CI job containers also reach it (200) but release gate is manual |
|
|
| Protocol document | Repo `docs/` | `.planning` raw outputs | human-readable German doc; raw JSON stays out of docs |
|
|
|
|
## Standard Stack (pinned, checked 2026-10-09)
|
|
| Tool | Version | Source / form | Notes |
|
|
|---|---|---|---|
|
|
| gitleaks | 8.30.1 (2026-03-21) | GitHub release tar.gz + `gitleaks_8.30.1_checksums.txt` | `gitleaks git --redact --exit-code 0` for full history [VERIFIED: ran it] |
|
|
| trivy | 0.75.0 (2026-10-01) | GitHub release `trivy_0.75.0_Linux-64bit.tar.gz` + checksums | prefer plain binary over `trivy-action`; DB from ghcr.io worked [VERIFIED] |
|
|
| osv-scanner | 2.6.0 (2026-09-14) | release binary + `osv-scanner_SHA256SUMS` | `scan source --lockfile pnpm-lock.yaml` [VERIFIED: ran it]; sends package list to osv.dev |
|
|
| semgrep | 1.180.0 (2026-10-07) | `pipx install semgrep==1.180.0` | `--metrics=off`; rule packs download from semgrep.dev [VERIFIED] |
|
|
| pnpm audit | pnpm 9.15 | already in CI | `--prod` split; uses npm audit endpoint, worked today [VERIFIED]; could be retired by registry -> osv-scanner is the fallback |
|
|
| OWASP ZAP | `ghcr.io/zaproxy/zaproxy:stable` = 2.17.0 (digest `sha256:7aaa659b0d43...`) | docker | pin by digest in script [VERIFIED: pulled] |
|
|
|
|
Package Legitimacy Audit: no npm/PyPI/crates dependency is added to the repo (binaries from official GitHub releases with checksum verification, `semgrep` installed with pipx in the throwaway job container). `semgrep` is a well-known PyPI package; version pinned. Nothing flagged. Do NOT use `aquasecurity/trivy-action`/marketplace actions: mutable tags are exactly what Semgrep flags in our own ci.yml, and Gitea resolves them from github.com. [ASSUMED: reason; the direct-binary route is verified]
|
|
|
|
## Architecture Patterns
|
|
|
|
### CI flow
|
|
```
|
|
push main / v* tag
|
|
quality -> test -> desktop -> publish (build+push images; images stay in host daemon)
|
|
|
|
|
v
|
|
security (needs: publish, continue-on-error, never gates anything)
|
|
checkout fetch-depth:0 -> install pinned tools (curl+sha256, pipx semgrep)
|
|
-> gitleaks git (history) -> osv-scanner (pnpm-lock.yaml + Cargo.lock)
|
|
-> pnpm audit --prod -> semgrep (5 packs)
|
|
-> trivy fs (misconfig+secret) -> trivy image api+web (host docker.sock)
|
|
-> print "SECURITY-SUMMARY tool=count" lines -> upload-artifact@v3 (best effort)
|
|
```
|
|
Runner is serial (one job at a time) so the job adds ~5-8 min to the queue after `publish`; placing it after `publish` guarantees it can never delay or block publishing/releases. Run only when `gitea.ref` is main or `v*` (images exist); for other refs skip the image step.
|
|
|
|
### Skeleton (planner adapts)
|
|
```yaml
|
|
security:
|
|
name: Sicherheitspruefung (nur Bericht)
|
|
runs-on: ubuntu-latest
|
|
needs: publish
|
|
continue-on-error: true # job level; steps below also swallow errors
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with: { fetch-depth: 0 } # gitleaks needs full history
|
|
- name: Werkzeuge installieren
|
|
run: sh .gitea/scripts/security-scan.sh install || true
|
|
- name: Pruefungen (nur Bericht)
|
|
run: sh .gitea/scripts/security-scan.sh run || true
|
|
- uses: actions/upload-artifact@v3 # v4 is rejected on Gitea (GHES detection)
|
|
if: always()
|
|
continue-on-error: true
|
|
with: { name: security-reports, path: security-reports/ }
|
|
```
|
|
Put logic in a script `.gitea/scripts/security-scan.sh` (same pattern as `publish-images.sh`; runnable locally; no secret handling). Inside: `export TRIVY_CACHE_DIR=/opt/hostedtoolcache/trivy`, `set +e`, every tool `--exit-code 0` or `|| true`, final `exit 0`. Exit codes to neutralise: osv-scanner exits 1 on findings, pnpm audit exits 1, trivy/gitleaks/semgrep exit 0 unless `--exit-code`/`--error` set (we do not set them). [VERIFIED for osv/pnpm audit/gitleaks/trivy/semgrep via local runs: pnpm audit rc=1; others rc 0 with `--exit-code 0`]
|
|
|
|
Ignore files to add so reports stay readable: `.gitleaks.toml` allowlist paths `apps/api/src/cert-manager/__fixtures__/`, `.planning/`; `.semgrepignore` (fixtures, `*.spec.ts`/`*.test.tsx`); `.trivyignore` not needed initially.
|
|
|
|
### Anti-patterns
|
|
- `docker run -v $PWD:/src ...` inside a job container: the path is a path inside the job container, the daemon is the host's -> empty mount. Use binaries (verified) or `--volumes-from "$(hostname)"` [ASSUMED, untested].
|
|
- Letting the Trivy cache grow: the 1.4 GB seen locally was mostly `fanal/` layer cache from the two image scans. `rm -rf $TRIVY_CACHE_DIR/fanal` at the end; keep only `db/`. The runner shares a disk that has filled before (memory `project_disk_cleanup`).
|
|
- Adding the job to `needs:` of anything, or `if: failure()` hooks: keeps it non-gating.
|
|
|
|
### Reporting channel
|
|
- Gitea 1.26.2 is installed; job summaries (`$GITHUB_STEP_SUMMARY`) need Gitea 1.27 + runner 2.0. [CITED: gitea.com/gitea/runner/pulls/917, blog.gitea.com/release-of-runner-2.0.0] -> not usable now. Use log lines (`SECURITY-SUMMARY gitleaks=20 osv=43 trivy_api=C6/H116 ...`) so counts are readable in the run log.
|
|
- `actions/upload-artifact@v4` is not supported on Gitea (detected as GHES); v3 or `christopherHX/gitea-upload-artifact@v4` work. [CITED: gitea.com/actions/gitea-upload-artifact, code.forgejo.org/forgejo/runner/issues/144] Our runner address is the internal `http://gitea:3000` (memory 24.09.); upstream notes artifact URLs can break with a non-public runner URL [CITED: same search result] -> treat artifacts as best effort, verify on first run, never rely on them. Existing `actions/cache/{save,restore}` is proven in this runner (cache host 172.18.0.1:42641) and is the fallback for passing files between jobs, not for reading them.
|
|
|
|
## Baseline results (raw files in `baseline/`, SUMMARY.txt there)
|
|
All run today against HEAD, no calls to our servers (only github/ghcr/semgrep.dev/osv.dev/npm registry). Gitleaks output redacted; Trivy/Semgrep excerpts of fixture keys redacted.
|
|
|
|
| Scan | Result |
|
|
|---|---|
|
|
| gitleaks full history (1367 commits, 24.5 MB, 6 s) | 20 hits, **0 real**: 16 `private-key` = 8 test fixtures in `apps/api/src/cert-manager/__fixtures__/` (+ 6 spec/RESEARCH snippets with `-----BEGIN` text), 3 `generic-api-key` = i18n label in `de.json:734`, test key `settings.authMethodLabel`, a table row in `12-VALIDATION.md`; 1 `curl-auth-user` = example against the local throwaway Nextcloud test container in `261008-mzu-RESEARCH.md:280` (docs only; consider masking). Gitleaks does not detect the formerly removed OWA IP (not a secret pattern). |
|
|
| pnpm audit (all) | 171 vulns / 151 advisories: C7 H85 M74 L5 (1268 deps) |
|
|
| pnpm audit --prod | 151: **C5 H73 M68 L5** across 30 packages |
|
|
| osv-scanner on pnpm-lock.yaml | 43 vulnerable package@version of 1262 |
|
|
| trivy fs | pnpm-lock = same 151 (C5 H73 M68 L5); Cargo.lock 2 MEDIUM; misconfig: DS-0026 "no HEALTHCHECK" LOW in `apps/api/Dockerfile` and `apps/web/Dockerfile`; 7 HIGH "secrets" = test key fixtures |
|
|
| trivy image api:beta | Node pkgs **C6 H116 M98 L7**, Alpine 3.24 OS pkgs M1, secrets 0 |
|
|
| trivy image web:beta | Node pkgs **C2 H19 M21 L1**, Alpine M1, secrets 0 |
|
|
| semgrep (280 rules, 1145 files, 3 min) | 58 findings (ERROR 9, WARNING 46, MEDIUM 3); 59 scan errors (43 partial parsing, 16 timeouts - use `--timeout`/accept) |
|
|
|
|
### Dependency triage (production, by package)
|
|
| Priority | Package (installed -> fix) | Path | Reachability / hint |
|
|
|---|---|---|---|
|
|
| P1 | **next 15.5.19 -> >=15.5.27** (2 crit RCE incl. image optimisation, SSRF, DoS, cache poisoning; 12 advisories) | direct `apps/web` (`^15.3.0`) | web is internet-facing behind NPM; same-major patch, low risk; also clears 2 crit + most of web image |
|
|
| P1 | proxy-addr 2.0.7 -> 2.0.8 (crit, IP spoofing) | express 5.2.1 via `@nestjs/platform-express` | only relevant if `trust proxy` is set; pnpm override |
|
|
| P1 | nodemailer 9.0.1 (+8.0.11 in a dep) -> >=9.1.1/10.0.6 (8 advisories) | direct api | mail with admin/user-influenced addresses |
|
|
| P1 | undici 7.28.0 -> 7.29.1 (21 advisories, SSRF-guarded fetch uses it) | direct api pin `7.28.0` | pin bump |
|
|
| P2 | handlebars 4.7.9 -> 4.7.10 (crit) | `@nestjs-modules/mailer` | needs attacker-controlled template; templates are admin-edited -> low reachability, still bump |
|
|
| P2 | multer 2.1.1 -> 2.3.0, axios 1.18.1 -> 1.20.0 (via `ews-javascript-api`), adm-zip 0.6.0 -> 0.6.1 (direct, cert-manager ZIP; `zip-expand.ts` has own limits per 261009-ikt review), brace-expansion, @xmldom/xmldom, ip-address, js-yaml, liquidjs, postcss, sharp, svgo, nanoid, browserslist, source-map-js, qs, uuid, moment, csv-parse | mostly transitive | mostly DoS/ReDoS; lockfile refresh / `pnpm.overrides` |
|
|
| accept/monitor | **xlsx 0.18.5** (2 high; npm has no fix, fixed 0.20.2 only on SheetJS CDN), **node-forge 1.4.0** (high, no fixed version listed), adm-zip symlink advisory (no fix) | direct api (`handelsware-xlsx.ts`, `doe-opendata.adapter.ts`; cert-manager) | decision needed: replace xlsx (exceljs/read-excel-file) or vendor CDN tarball; document as accepted risk in protocol |
|
|
| dev-only / image hygiene | tinypool (crit, vitest), tar 6.2.1 (crit), pnpm 9.15.9 (12 high) appear in the **api image** = devDependencies + corepack pnpm shipped in runtime image | `apps/api/Dockerfile` | follow-up: install prod-only (`pnpm deploy --prod`) and drop pnpm from the final stage; shrinks 1.66 GB image and cuts api C6/H116 sharply |
|
|
|
|
### Semgrep triage (non-test source)
|
|
| Rule | Where | Verdict |
|
|
|---|---|---|
|
|
| gcm-no-tag-length | `apps/api/src/crypto/crypto.service.ts:97` (`createDecipheriv('aes-256-gcm'...)`, authTag from stored value, no `authTagLength`) | **true positive (hardening, low)**: attacker needs DB write; fix = pass `{ authTagLength: 16 }` and check length |
|
|
| bypass-tls-verification (4) | `ldap.service.ts:182`, `proxmox-auth.ts:84`, `proxmox-client.service.ts:173`, `icon-discovery.service.ts:62` | accepted by design: opt-in per connection (`tlsRejectUnauthorized`), comments cite decisions D-04/T-JDD-01; document in protocol |
|
|
| js-open-redirect | `apps/web/.../login/page.tsx:36` | false positive: `sanitizeNextPath()` in `lib/safe-next.ts` rejects non-`/`, `//`, `/\` |
|
|
| detect-non-literal-regexp (3 prod) | `exchange.provider.ts:85`, `exchange-inbox.provider.ts:165,179` | false positive: `attr` is a code constant, not user input |
|
|
| prototype-pollution-loop | `autodns-parse.ts:144` | false positive (read-only path walk) |
|
|
| gha-curl-pipe-shell + 12 mutable-action-tag | `.gitea/workflows/ci.yml` (rustup install; `actions/*@v4`) | low, CI hygiene; pinning optional |
|
|
| pnpm-workspace hardening (3) | `pnpm-workspace.yaml`: `minimumReleaseAge`, `blockExoticSubdeps`, `trustPolicy` not set | useful low-cost supply-chain hardening; needs pnpm >=10.x semantics - check before adopting (pnpm is 9.15.0) [ASSUMED] |
|
|
| test/fixture hits | private-key fixtures (7), spec/test files, HTML fixture links | ignore via `.semgrepignore` |
|
|
|
|
## Inventory of existing security work (for the protocol)
|
|
[VERIFIED by reading frontmatter/fix sections of each file this session; counts = critical/warning/info]
|
|
|
|
| Date | Artefact | Scope | Findings | Fix status |
|
|
|---|---|---|---|---|
|
|
| 2026-06-27 | `phases/07-dkv-fleet-module/07-REVIEW.md` + `07-REVIEW-FIX.md` | 44 files DKV module | C3 W5 I4 (12) | 8/8 C+W fixed (`all_fixed`) |
|
|
| 2026-07-01 | `phases/08-dashboard-widgets.../08-REVIEW.md` | 24 files widgets, favorites, icon-discovery | C2 W5 I3 (10) | status `issues_found`; fix record not located -> executor must check |
|
|
| 2026-08-06 | `phases/16-ad-gruppen-synchronisation/16-REVIEW.md` | 21 files LDAP/groups | C0 W4 I2 (6) | warnings fixed 2026-08-06, info open |
|
|
| 2026-09-16 | `phases/18-desktop-client-fertigstellen/18-REVIEW.md` + `-FIX` | 23 files updater/desktop/Dockerfile | C1 W3 I2 (6) | 4 fixed, 2 info skipped, status clean |
|
|
| 2026-10-08 | `quick/261008-dts-.../261008-dts-REVIEW.md` | 37 files Domains/AutoDNS | C1 W6 I4 (11) | "Fix Status" table present (CR-01, WR-01, WR-02 fixed in `cc1c83a`); remaining rows not all read |
|
|
| 2026-10-08 | `quick/261008-mzu-.../261008-mzu-REVIEW.md` | 54 files Nextcloud-Dateien | C2 W9 I7 (18) | `all_fixed`, 12 commits |
|
|
| 2026-10-09 | `quick/261009-dkv-.../261009-dkv-REVIEW.md` | Nextcloud shares (5 commits) | C1 W4 I6 (11) | CR-01, WR-01, WR-02 fixed (`78f6cf3`, `d487a00`); info rows unverified |
|
|
| 2026-10-09 | `quick/261009-ikt-.../261009-ikt-REVIEW.md` | 62 files Cert-Manager (SSRF/ZIP) | C3 W7 I5 (15) | fixed (`bfcf6d4`, `c5bffe9`), each blocker has regression test; SSRF design in `cert-aia.ts` reproduced as sound |
|
|
| Total reviews | 8 | | **C13 W43 I33 = 89** | |
|
|
|
|
Other evidence (cite in protocol):
|
|
- **Threat models:** 178 PLAN files carry a `<threat_model>`/STRIDE section; 872 distinct `T-xxx-nn` threat IDs in PLANs. [VERIFIED: grep counts]
|
|
- **Tenant isolation (RLS), quick tasks 260909-dgj ... 260914-eym** (about 20 tasks, Etappen 1/2/3b/3c, switch OFF per memory): DB role `tessera_app` without superuser/BYPASSRLS, RLS on all 20 `tenantId` tables, `apps/api/scripts/rls-preflight.mjs` (5 proofs), guarded by `rls-app-role.spec.ts`, `rls-coverage.spec.ts`, `rls-preflight.spec.ts`, `rls-access-inventory.spec.ts` (72 file/model pairs, 4 detection forms), `auth-lookup-functions.spec.ts`. Docs: `docs/mandantentrennung-*.md`.
|
|
- **Other security quick tasks:** 260701 calendar SSRF fix; 260921-oxm IMAP STARTTLS enforced; 260921-fi3 forced password change enforced at API; 260914-ebg SUPER_ADMIN target-role guard (privilege escalation, WINDOWS #29); 260911-mkj RLS relation blind spot (WINDOWS #27); 260921-m34 288 `any` triaged; 260630-gbh password/avatar scoping; 260909-cx0 file-backup volume.
|
|
- **Ledger `.planning/WINDOWS.md`:** 39 entries, 25 fixed, 13 open, 1 waived (as of 2026-09-21); security-relevant: #18-#20 (RLS bypass by app role, extension connection), #22, #23, #29, #33. Open ones are mostly un-run browser checks; executor should list which are security-relevant.
|
|
- **Security-flavoured automated tests:** 44 spec/test files match ssrf|redos|zip|traversal|injection|xss|rls (cert-manager, nextcloud transfer, favorites `isPublicHttpUrl`, proxmox "nur lesen", user/guard specs, tender adapters). E2E scripts under `.planning/quick/{w5w,mzu,ikt,who,dkv}-*/e2e/`. Milestone audit: `.planning/v1.1-MILESTONE-AUDIT.md`. Per-quick `*-VERIFICATION.md` (~50).
|
|
- No `*-SECURITY.md` files exist (`/gsd-secure-phase` never run) -> protocol should say so plainly.
|
|
|
|
## ZAP baseline against alpha
|
|
- **Reachability (single-shot checks):** `curl -sI https://alpha.tessera.ctl.de/` from the dev host -> `HTTP/2 307 location: /login`, `/login` -> **200** (resolved to 217.7.63.32; no `WWW-Authenticate`). Same 200 from a container on the default bridge and on network `gitea`. So internal/hairpin sources are excepted from Basic Auth today; **no credentials needed, Basic Auth untouched**. [VERIFIED: curl/ docker run curlimages/curl]
|
|
- **Command (local script, run before a release):**
|
|
```bash
|
|
mkdir -p out && chmod 777 out # image runs as uid 1000 (zap); /zap/wrk is NOT in the image
|
|
docker run --rm -v "$PWD/out:/zap/wrk:rw" -t ghcr.io/zaproxy/zaproxy@sha256:7aaa659b0d43... \
|
|
zap-baseline.py -t https://alpha.tessera.ctl.de -m 5 -I -j -T 15 \
|
|
-r zap.html -w zap.md -J zap.json
|
|
```
|
|
Options [VERIFIED via `zap-baseline.py -h` and docs www.zaproxy.org/docs/docker/baseline-scan/]: `-m` spider minutes (default 1), `-I` never fail on WARN (exit codes: 0 ok, 1 FAIL, 2 WARN, 3 error; script must still `exit 0` after copying reports), `-j` extra AJAX spider (matters for Next.js SPA; adds time), `-r/-w/-J/-x` HTML/Markdown/JSON/XML. Passive only (spider + passive scan; no attacks). Measured: tiny local site with `-m 1` = 38 s; alpha with `-m 5 -j` plan for ~8-10 min. Unauthenticated: it will mostly see `/login` + assets; authenticated coverage needs a login context (optional later, would need a dedicated low-privilege test user).
|
|
- **If alpha ever stops excepting the source IP** (tested end to end against a local header-logging server): `-z "-config replacer..."` is **silently ignored** in 2.17 (automation-framework mode) - header arrived as `None`. What works: `--autooff --hook=/zap/wrk/hook.py` with `zap.replacer.add_rule(... matchtype='REQ_HEADER', matchstring='Authorization', replacement=os.environ['ZAP_BASIC_AUTH'])`, passing `-e ZAP_BASIC_AUTH='Basic <b64>'` from a file outside git; server log showed `auth=YES` on every request. Hook saved as `baseline/zap-hook-basic-auth.py`. [VERIFIED]
|
|
- Do not run it from the `gitea` runner by default: needs `/zap/wrk` writable volume (bind-mount pitfall above) and 3.8 GB image pull. Keep it a manual pre-release step; protocol lists date + counts per run. Provide a 'ZAP' line in `docs/anleitung-betrieb.md` chapter 9 (release steps).
|
|
|
|
## Don't Hand-Roll
|
|
| Problem | Use | Why |
|
|
|---|---|---|
|
|
| secret detection in history | gitleaks | entropy + rules, redaction |
|
|
| lockfile CVE matching | osv-scanner / trivy / pnpm audit | advisory DBs |
|
|
| SAST | semgrep registry packs | maintained rules |
|
|
| image CVEs | trivy image | OS + language layers |
|
|
| report assembly | a ~40-line script reading the JSONs with jq/python | do NOT build a dashboard |
|
|
|
|
## Common Pitfalls
|
|
1. **Bind-mount path trap** (above) - verify first run with `ls` of what the scanner sees.
|
|
2. **Rate limits on first run:** Trivy DB (ghcr.io) and OSV/npm; cache DB in toolcache; failures must not fail the job (they only drop that report).
|
|
3. **Timing:** every push already takes 6-13 min and Gitea runs jobs serially; the Tag push triggers 3 runs -> 3 security runs. Consider `if: gitea.ref == 'refs/heads/main' || startsWith(gitea.ref,'refs/tags/v')` and skip on branch `live` (same commit as the tag).
|
|
4. **Noise destroys the protocol:** commit the allowlists first; baseline numbers in the protocol must say "after ignoring test fixtures".
|
|
5. **Raw JSON size:** `baseline/` is 6.7 MB; commit only `SUMMARY.txt` + (optionally) compact CSV/MD digests, keep big JSON out of git (or gitignore) - planner decides.
|
|
6. Protocol audience is non-technical: translate (Kritisch = "muss zeitnah behoben werden"), explain "Testschluessel = harmlos", never paste advisory text.
|
|
|
|
## Environment Availability
|
|
| Dependency | Needed by | Available | Version | Fallback |
|
|
|---|---|---|---|---|
|
|
| docker (host) | local baseline, ZAP | yes | 29.8.0 | — |
|
|
| Runner job image tools (python3, pipx, jq, curl, docker.sock) | CI step | yes | py 3.12.3, docker CLI 29.5.2 | — |
|
|
| github.com / ghcr.io / semgrep.dev / osv.dev from job network | downloads | yes (probe ok) | — | mirror binaries in toolcache |
|
|
| pnpm audit endpoint | pnpm audit | yes | — | osv-scanner |
|
|
| alpha reachable from dev host + `gitea` net | ZAP | yes (200) | — | ZAP hook with Basic Auth header |
|
|
| Disk | caches/images | 45 GB free (74 % used) | — | clear `fanal` cache; ZAP image 3.8 GB, semgrep 1.6 GB pulled locally |
|
|
Local images pulled for this research (can be pruned, `docker image prune -f`, never `-a`): trivy, osv-scanner, semgrep, gitleaks, zaproxy, curlimages/curl.
|
|
|
|
## Assumptions Log
|
|
| # | Claim | Risk if wrong |
|
|
|---|---|---|
|
|
| A1 | Job container reaches the HOST docker daemon through the mounted socket, so `trivy image localhost:3002/...:beta` finds the just-built tags (mount verified; trivy-in-job-container not run) | image scan step needs `docker save`/`pull` fallback |
|
|
| A2 | `act-toolcache` volume persists across jobs so Trivy DB cache survives | DB re-downloaded each run (~1 min), harmless |
|
|
| A3 | Job-level `continue-on-error: true` keeps the run green in Gitea 1.26 (step-level `|| true` makes it moot) | red job icon only, nothing gates on it |
|
|
| A4 | `upload-artifact@v3` works with runner address `http://gitea:3000` | no downloadable reports; log lines remain |
|
|
| A5 | pnpm-workspace hardening keys (`minimumReleaseAge` etc.) require pnpm >=10 | adoption must be tested |
|
|
| A6 | Reason to avoid marketplace actions (mutable tags / supply chain) | none, binaries route is verified anyway |
|
|
| A7 | Next 15.5.27 is the highest 15.x fix; verify with `npm view next@15 version` before pinning | wrong target version |
|
|
| A8 | ZAP spider does not submit forms in baseline mode (passive) | would create data on alpha; check report |
|
|
|
|
## Open Questions
|
|
1. **xlsx / node-forge no-fix:** replace xlsx (needs behaviour tests for DATEV/DOE imports) or accept? Recommend: protocol entry "accepted until replaced", follow-up quick task.
|
|
2. **Fix the P1 dependency list now or only record?** The wish is reporting only for CI; remediation is a separate quick task (Next bump first). Planner should add "Befund-Abarbeitung" as follow-up, not inside this task.
|
|
3. Should `docs/sicherheitsprotokoll.md` include a "Wiederholung" cadence (weekly `schedule:` workflow run for new CVEs)? Recommend a weekly cron in a second tiny workflow later; not needed for v1.
|
|
|
|
## Validation Architecture
|
|
Framework: shell/CI-level only. Checks: (a) `sh .gitea/scripts/security-scan.sh run` locally exits 0 even with findings and writes `security-reports/`; (b) `GITHUB_REF=refs/heads/feature sh ... run` skips image scans; (c) YAML lint via existing push; (d) first CI run: job green, `SECURITY-SUMMARY` lines visible, publish unaffected (job is after it). Doc check: German "Sie", no secret values, links from `docs/README.md` and guides resolve. Wave 0: none (no test framework needed).
|
|
|
|
## Security Domain
|
|
Not an application-code phase; the deliverable is process/CI. Controls: scanners run with no registry credentials in env (the `security` job must NOT receive `REGISTRY_TOKEN` or Tauri secrets); gitleaks `--redact`; reports never echo secret values; job runs after publish and cannot alter images.
|
|
|
|
## Sources
|
|
- Primary (measured this session): runner/job container `docker inspect`, probe script in `gitea/runner-images:ubuntu-latest`, local scanner runs, `docker run zaproxy ... -h`, ZAP hook test.
|
|
- [CITED] www.zaproxy.org/docs/docker/baseline-scan/ (options, exit codes); GitHub releases API for gitleaks v8.30.1, trivy v0.75.0, osv-scanner v2.6.0, semgrep v1.180.0, ZAP v2.17.0.
|
|
- [CITED] gitea.com/actions/gitea-upload-artifact; code.forgejo.org/forgejo/runner/issues/144 (upload-artifact v4 vs GHES); gitea.com/gitea/runner/pulls/917 and blog.gitea.com/release-of-runner-2.0.0 (job summary needs Gitea 1.27).
|
|
|
|
**Valid until:** 2026-10-16 for vulnerability counts (advisories change daily); 30 days for tool/CI mechanics.
|