Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
27 KiB
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.)
- Separate document
docs/sicherheitsprotokoll.md(linked fromdocs/README.mdand the guides): ALL security checks done so far + a baseline full scan, kept current. Audience: non-programmer owner and later customers -> plain German, "Sie". - 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. - OWASP ZAP baseline against alpha (
https://alpha.tessera.ctl.de) before releases, scripted. Basic Auth in front of alpha must NOT be removed. - 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 agit archive HEADcopy, history scan reads.gitonly). - Gitea is reached via
localhost:3002(host) / host gateway :3002 (job containers), never viagit.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)
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/fanalat the end; keep onlydb/. The runner shares a disk that has filled before (memoryproject_disk_cleanup). - Adding the job to
needs:of anything, orif: 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@v4is not supported on Gitea (detected as GHES); v3 orchristopherHX/gitea-upload-artifact@v4work. [CITED: gitea.com/actions/gitea-upload-artifact, code.forgejo.org/forgejo/runner/issues/144] Our runner address is the internalhttp://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. Existingactions/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 distinctT-xxx-nnthreat 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_appwithout superuser/BYPASSRLS, RLS on all 20tenantIdtables,apps/api/scripts/rls-preflight.mjs(5 proofs), guarded byrls-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
anytriaged; 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.mdfiles exist (/gsd-secure-phasenever 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; noWWW-Authenticate). Same 200 from a container on the default bridge and on networkgitea. 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):
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 asNone. What works:--autooff --hook=/zap/wrk/hook.pywithzap.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 showedauth=YESon every request. Hook saved asbaseline/zap-hook-basic-auth.py. [VERIFIED] - Do not run it from the
gitearunner by default: needs/zap/wrkwritable 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 indocs/anleitung-betrieb.mdchapter 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
- Bind-mount path trap (above) - verify first run with
lsof what the scanner sees. - 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).
- 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 branchlive(same commit as the tag). - Noise destroys the protocol: commit the allowlists first; baseline numbers in the protocol must say "after ignoring test fixtures".
- Raw JSON size:
baseline/is 6.7 MB; commit onlySUMMARY.txt+ (optionally) compact CSV/MD digests, keep big JSON out of git (or gitignore) - planner decides. - 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 ` |
|
| 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
- 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.
- 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.
- Should
docs/sicherheitsprotokoll.mdinclude a "Wiederholung" cadence (weeklyschedule: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 ingitea/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.