Skip to content

security: merge the hardening branch into main (2/2 — after insight-front#258) - #2143

Merged
Gregory91G merged 23 commits into
mainfrom
security-hardening
Aug 3, 2026
Merged

security: merge the hardening branch into main (2/2 — after insight-front#258)#2143
Gregory91G merged 23 commits into
mainfrom
security-hardening

Conversation

@Gregory91G

@Gregory91G Gregory91G commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Merges the security-hardening branch into main. Eight PRs, taken out of the 2026-07-28 release
and collected here since. Tracked by #2020 and #2079.

PR What
#2083 nltk and langchain-core resolved to fixed versions through [tool.uv] override-dependencies on 8 projects, plus a 7-day exclude-newer cooldown
#2085 securityContext for the fakeidp and keycloak Deployments
#2089 Four tooling images run as a non-root user; --no-install-recommends on the toolbox nodejs install; a path-scoped waiver for the e2e runner in .trivyignore.yaml
#2093 62 action references pinned to commit digests across six workflows; Dependabot with a 7-day cooldown; persist-credentials: false on all 19 checkout steps; the scanner-refresh SOP
#2094 The same digest pinning in build-images.yml, kept separate because it publishes every image and the umbrella chart
#2095 The frontend chart runs as a non-root user on 8080
#2119 readOnlyRootFilesystem and capabilities.drop: ["ALL"] on every container in the analytics, authenticator and identity-resolution charts, including both CronJobs and the authn-tls sidecar, with emptyDir volumes on exactly the paths each container writes
#2121 cryptography override for the connector harness; deploy/seed raised past the mypy/dbt-core pathspec split
--ignorefile moved onto the trivy command; it sat among the docker run flags behind a literal `
`, so both scan jobs exited 125 and no SARIF reached Code Scanning

32 files, +461/−132. Merges into main with no conflicts (verified in a scratch worktree off
origin/main).

Merge order — one hard constraint

constructorfabric/insight-front's security-hardening must merge first, and this should
follow immediately.

The frontend chart lives here; the image it deploys lives there. #2095 sets containerPort: 8080
and runAsUser: 101; insight-front#252 moves the image to unprivileged nginx on 8080. Until both
are on main and one umbrella chart publish has happened after both, the chart and the image name
different ports and the frontend pod does not become ready in insight-dev.

This merge touches 17 files under the build-images.yml path filters, so it republishes the
umbrella chart on its own. Do not leave a gitops cycle between the two merges. If the window needs
closing by hand, fire build-images.yml via workflow_dispatch with the current frontend_tag
once both are in.

What this does not close

Three findings stay open deliberately, each recorded with its reasoning:

  • AVD-KSV-0014 on keycloak — accepted risk. start-dev re-augments Quarkus into
    /opt/keycloak/lib/quarkus on every boot; measured, a read-only root throws
    ReadOnlyFileSystemException, and an emptyDir hides the file the image ships. Not suppressed,
    so it stays visible in every scan.
  • AVD-KSV-0014 on the frontend chart, same shape, also measured.
  • AVD-DS-0002 on the e2e runner image — not applicable, decided by QA; waived path-scoped in
    .trivyignore.yaml.

Separately, roughly 22 HIGH live inside the built insight-toolbox image (tar, minimatch,
kubectl, yq and friends under usr/lib/node_modules and usr/local/bin). Those come from the
image scan, not from anything in this tree, and no PR here addresses them.

Test plan

  • security-hardening merges into origin/main with no conflicts
  • Every constituent PR was verified on its own branch before merge — see each PR's test plan
  • trivy fs --scanners vuln --include-dev-deps over all 11 Python projects, locks regenerated
    from clean exactly as trivy.yml does: 0 CRITICAL, 0 HIGH
  • trivy fs --scanners misconfig over the rendered charts: 17 HIGH → 10, all seven
    KSV-0014 on analytics / authenticator / identity-resolution cleared; the remainder is
    keycloak and the frontend chart above
  • helm lint clean on every chart; the identity-resolution CronJob contract suite passes
    (17 tests)
  • Connector harness pytest --suites-only: 42 passed, 1 skipped — the pre-change baseline
  • After merge: CI green on main
  • After merge: build-images.yml publishes the umbrella chart
  • After merge: every service pod in insight-dev reaches Ready under the new
    securityContext — analytics, authenticator, identity-resolution, fakeidp, keycloak,
    frontend
  • After merge: the nightly trivy.yml run against main reports 0 CRITICAL and roughly 25
    HIGH, down from 9 and 131

Summary by CodeRabbit

  • Security

    • Strengthened container isolation with non-root execution, read-only filesystems, reduced privileges, and dropped capabilities across services and tools.
    • Improved workflow integrity by pinning automation actions to immutable versions and tightening credential handling.
    • Added scoped vulnerability-scan exclusions and explicit scan configuration.
  • Maintenance

    • Added scheduled dependency and image update guidance.
    • Improved dependency resolution safeguards and refreshed development tooling compatibility.
  • Documentation

    • Added operational guidance for regularly refreshing scanner image references.

Gregory91G and others added 21 commits July 31, 2026 19:44
…#2118)

Set readOnlyRootFilesystem and capabilities.drop: ["ALL"] on every container
in the analytics, authenticator and identity-resolution charts, with emptyDir
volumes on exactly the paths each container writes.

Writes were enumerated with `docker diff` on the published images against a
local mariadb/clickhouse/redis/fakeidp stack, driven by the chart's own
rendered ConfigMap:

  /app/data          server.home_dir     -- every container
  /tmp/<svc>-grpc    grpc-hub UDS        -- the three long-running servers
  /tmp               pid + *_temp        -- the authn-tls nginx sidecar

migrate, persons-seed and persons-sync never start grpc-hub, so they mount
/app/data only. With /app/data but no /tmp the servers stay Running with both
probes green while grpc-hub fails to bind its socket, so that volume is
load-bearing rather than defensive.

The authn-tls sidecar gets the same two controls and its own /tmp volume,
kept separate from the authenticator's: that one carries the grpc-hub socket.
It reaches the scan only when tlsDiscovery is enabled, which the umbrella does
and the subchart defaults do not.

Rendered-chart misconfig scan: 17 HIGH -> 10, clearing all seven KSV-0014 on
these charts. helm lint clean; the seed/sync CronJob contract suite passes.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
)

airbyte-cdk pins nltk==3.9.1 in every CDK connector, and additionally
langchain-core==0.1.42 in the 6.60.x line the nocode test harness runs on.
Both pins have a released fix for a CRITICAL (CVE-2025-14009,
CVE-2025-68664), and no downstream project can move them by declaring a
version floor.

Declare uv override-dependencies so a resolved graph lands on the fixed
versions. For the connectors this also makes the declared graph agree with
the image, which already installs nltk 3.10.0 over the pin with --no-deps.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
A version pushed in the last few days is where a compromised maintainer account
surfaces first, and an override that only sets a floor would resolve straight to it.

Verified the cooldown does not undo the point of the override: resolution still
lands on nltk 3.10.0 and a langchain-core above the fix, with the newest release
held back as intended.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
Both Deployments ran with the default context, so a container could escalate
privileges and kept the full capability set. Adopt the shape already used by
the gateway chart: non-root UID/GID 1000 at pod level, no privilege
escalation and all capabilities dropped at container level.

fakeidp additionally gets readOnlyRootFilesystem — it never writes to disk.
keycloak does not: start-dev re-augments Quarkus into /opt/keycloak/lib/quarkus
on every boot, and backing that path with an emptyDir hides the image's
quarkus-application.dat.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
seed and toolbox run as Kubernetes Jobs and mount nothing from a host, so a
fixed UID is unambiguous there. declarative-connector receives only read-only
mounts and writes to stdout. rust-watch keeps its writable paths in named
volumes, which Docker seeds from the image including ownership, so /target and
the cargo caches are chowned before the volume is created.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
#1340)

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
Decided by QA: the image is not part of the delivered product, so the condition the
rule guards against does not arise. Scoped to that one path, with the reasoning in
the waiver itself rather than in a separate document that would drift from it.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
The YAML ignore file is the only form that can scope a waiver to a path, and Trivy
does not auto-detect it — verified: the waiver is ignored without --ignorefile and
applied with it. The file is committed, so the absent-file FATAL cannot trigger.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
They do not exist in rust:1.95-bookworm — /usr/local/cargo holds only bin and env —
so chown -R exited non-zero and broke the build.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
Same change as the rest of the workflows, kept apart because this file publishes
every image and the umbrella chart: a problem here stops delivery rather than a
test, so it needs to be reviewable and revertable on its own.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
The Deployment declared no securityContext, so nginx kept root and the full
capability set. Pairs with the unprivileged image in constructorfabric/insight-front:
uid 101, port 8080.

Only the container port moves. The Service resolves the container by port name and
still listens on 80, so the gateway's frontUrl is unchanged.

readOnlyRootFilesystem is left off: the entrypoint renders the nginx config from
/etc/nginx/templates at start and nginx needs a writable cache directory.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
…akeidp-keycloak

fix(helm): declare securityContext for fakeidp and keycloak (#2084)
…nroot

fix(images): run the tooling images as a non-root user
…images

ci(security): pin third-party actions to digests in build-images.yml
…curitycontext

fix(helm): run the frontend as a non-root user on 8080 (#1340)
…2120)

airbyte-cdk 6.60.x resolves cryptography 44.0.3, which carries a Bleichenbacher
timing oracle in PKCS#1 v1.5 decryption (CVE-2026-26007, fixed in 46.0.5) and
GHSA-537c-gmf6-5ccf (fixed in 48.0.1). The override resolves 49.0.0.

Regenerating the lock from a clean checkout, as trivy.yml does, and rescanning:
both findings clear. Harness suites with the override applied: 42 passed,
1 skipped -- identical to baseline.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
* ci(security): pin third-party actions to digests, add Dependabot

A tag is a moving reference: the action's owner can repoint it, and so can whoever
takes over that account, after which the repointed code runs on our runners with the
job's token. A digest cannot be repointed.

Pinning alone would freeze these actions, so Dependabot refreshes them weekly, in two
groups — actions and base images — because a base-image bump is a rebuild that can
change runtime behaviour and must not block an unrelated action bump.

Covers six workflows. build-images.yml is pinned separately: it publishes every image
and the umbrella chart, so it is kept reviewable and revertable on its own.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

* ci(security): add a Dependabot cooldown, route review through CODEOWNERS

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

* ci(security): let Dependabot refresh the npm tree

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

* ci(security): drop the npm ecosystem from this repo (no package manifest is tracked)

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

* ci(security): drop persisted checkout credentials, correct the ADR and SOP

actions/checkout leaves the job token in .git/config by default, so any later step —
including a third-party action — can read it. None of these workflows write back, so
nothing depended on it.

Also: an action bump can change behaviour too, the two-per-week figure covers scheduled
updates only, and the SOP grep missed any image whose tag is not purely numeric.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

* chore(ci): drop the ADR and the .NET restore output from this branch

The ADR restated a practice the workflows already carry in comments, so it
documented convention rather than a decision. Nothing referenced it.

The `obj/` files under services/identity are NuGet restore output for a
service the tree no longer contains -- a stale working directory swept in by
a broad `git add`. They embed machine-local paths and were never meant to be
tracked.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>

---------

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
Co-authored-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
…emaining-charts

fix(helm): read-only root filesystem for the remaining backend charts (#2118)
…itical-cves

fix(ingestion): resolve nltk and langchain-core to fixed versions (#2061)
…rrent (#2120)

The scan reports eight highs on deploy/seed for jinja2 2.11.3, werkzeug 2.1.2
and sqlparse 0.4.3 -- a 2022 dependency set nothing installs. uv resolves the
base dependencies and the dev extra together, and mypy==2.1.0 needs
pathspec>=1.0.0 while dbt-core on the 1.11 line caps pathspec below 0.13. The
pin could not move, so dbt-core gave: the resolver walked it back to 1.2.6,
before that constraint existed.

dbt-core 1.12 raised its own cap to pathspec<1.1 (dbt-labs/dbt-core#12385), so a
current mypy resolves against a current dbt-core. The extra now asks for
mypy>=2,<3 and the graph comes out at dbt-core 1.12.0, dbt-adapters 1.24.5,
pathspec 1.0.4, mypy 2.3.0. dbt-clickhouse settles on 1.9.3 rather than 1.10.1:
the 1.10 line caps dbt-adapters below 1.23.0, under dbt-core 1.12.0's 1.24.5
floor, and 1.9.3 carries no adapter bound at all.

ruff moves 0.15.16 -> 0.15.21 to match the ruff-pre-commit hook, which was
already on v0.15.21. Two different pins meant a local run and the commit hook
could reach different verdicts.

Locking from clean: dbt-clickhouse 1.9.3, dbt-core 1.12.0, jinja2 3.1.6,
sqlparse 0.5.5, no werkzeug -- 0 CRITICAL and 0 HIGH repo-wide. The documented
recipe still works end to end: `pip install -e '.[dev]'` then `ruff check .`
(All checks passed) and `mypy .` (14 files, no issues).

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
…aining-highs

fix(deps): clear the remaining uv.lock highs — cryptography override, seed resolution (#2120)
@Gregory91G
Gregory91G requested a review from a team as a code owner August 3, 2026 14:52
@Gregory91G Gregory91G self-assigned this Aug 3, 2026
@Gregory91G
Gregory91G requested a review from cyberantonz August 3, 2026 14:53
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc5572c3-22d2-4e4e-a973-88b4716be927

📥 Commits

Reviewing files that changed from the base of the PR and between 6db8c52 and ffd71f6.

📒 Files selected for processing (7)
  • .github/workflows/build-images.yml
  • .github/workflows/ci.yml
  • .github/workflows/trivy.yml
  • src/backend/services/identity-resolution/helm/templates/deployment.yaml
  • src/backend/services/identity-resolution/helm/templates/seed-cronjob.yaml
  • src/backend/services/identity-resolution/helm/templates/sync-cronjob.yaml
  • src/frontend/helm/templates/deployment.yaml
💤 Files with no reviewable changes (1)
  • src/frontend/helm/templates/deployment.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/backend/services/identity-resolution/helm/templates/sync-cronjob.yaml
  • src/backend/services/identity-resolution/helm/templates/deployment.yaml
  • .github/workflows/trivy.yml
  • src/backend/services/identity-resolution/helm/templates/seed-cronjob.yaml
  • .github/workflows/ci.yml

📝 Walkthrough

Walkthrough

The PR hardens CI action references, dependency resolution, Docker images, Kubernetes workloads, and Trivy scanning. It adds Dependabot scheduling, scoped scanner waivers, non-root execution, read-only filesystems, dropped capabilities, and writable runtime volumes.

Changes

Security and dependency hardening

Layer / File(s) Summary
Automation supply-chain controls
.github/dependabot.yml, .github/workflows/*, docs/components/deployment/specs/sop/scanner-image-refresh.md
Dependabot schedules grouped weekly updates. GitHub Actions use immutable commit SHAs. Scanner image refresh procedures are documented.
Trivy waiver integration
.github/workflows/trivy.yml, .trivyignore.yaml
Trivy scans explicitly load the committed path-scoped ignore file. The file waives AVD-DS-0002 for the e2e runner Dockerfile.
Kubernetes workload hardening
src/backend/services/*/helm/templates/*.yaml, src/frontend/helm/templates/deployment.yaml
Deployments and CronJobs add non-root settings, read-only roots, dropped capabilities, disabled privilege escalation, and writable emptyDir mounts where required. The frontend port changes to 8080.
Container image hardening
deploy/*/Dockerfile, src/ingestion/tools/*/Dockerfile
Development and tooling images create non-root users, assign writable directory ownership, and reduce Node.js package installation scope.
Dependency resolution controls
deploy/seed/pyproject.toml, src/ingestion/**/pyproject.toml
uv resolution excludes releases from the previous seven days and enforces minimum dependency versions. Seed development dependency pins are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: ktursunov, cyberantonz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the security hardening merge into main and provides relevant sequencing context.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-images.yml:
- Around line 74-75: Update the actions/checkout steps in the build jobs at the
specified unprotected locations to set persist-credentials to false, including
the checkout adjacent to dorny/paths-filter. Leave the checkout steps used for
pushing at the later protected locations unchanged so they retain credential
persistence.
- Around line 996-999: Update both actions/create-github-app-token steps in the
workflow to explicitly request only repository Contents access by adding
permission-contents: write alongside the existing app-id and private-key inputs.
Do not add any other permission-* inputs, preserving the token’s use for
checkout and push.

In @.github/workflows/trivy.yml:
- Line 48: Update the comment adjacent to the Trivy ignore-file configuration to
describe the committed .trivyignore.yaml file and its path-scoped AVD-DS-0002
waiver, removing outdated references to .trivyignore and date-scoped CVE syntax.
- Around line 48-53: Move the --ignorefile argument in each Trivy filesystem
scan command after "$TRIVY_IMAGE" and the fs subcommand, including the scan
blocks near the referenced locations, so Docker receives only its own options
and Trivy receives the ignore-file option.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a2ba549-4f3d-42be-8d4e-bff8225296e3

📥 Commits

Reviewing files that changed from the base of the PR and between d9e4ae1 and 6db8c52.

📒 Files selected for processing (32)
  • .github/dependabot.yml
  • .github/workflows/authenticator.yml
  • .github/workflows/build-images.yml
  • .github/workflows/ci.yml
  • .github/workflows/e2e-bronze-to-api.yml
  • .github/workflows/gateway.yml
  • .github/workflows/ghcr-cleanup.yml
  • .github/workflows/openapi-specs.yml
  • .github/workflows/trivy.yml
  • .trivyignore.yaml
  • deploy/compose/rust-watch.Dockerfile
  • deploy/seed/Dockerfile
  • deploy/seed/pyproject.toml
  • docs/components/deployment/specs/sop/scanner-image-refresh.md
  • src/backend/services/analytics/helm/templates/deployment.yaml
  • src/backend/services/authenticator/helm/templates/deployment.yaml
  • src/backend/services/fakeidp/helm/templates/deployment.yaml
  • src/backend/services/identity-resolution/helm/templates/deployment.yaml
  • src/backend/services/identity-resolution/helm/templates/seed-cronjob.yaml
  • src/backend/services/identity-resolution/helm/templates/sync-cronjob.yaml
  • src/backend/services/keycloak/helm/templates/deployment.yaml
  • src/frontend/helm/templates/deployment.yaml
  • src/ingestion/connectors/ai/github-copilot/pyproject.toml
  • src/ingestion/connectors/crm/hubspot/pyproject.toml
  • src/ingestion/connectors/crm/salesforce/pyproject.toml
  • src/ingestion/connectors/git/bitbucket-cloud/pyproject.toml
  • src/ingestion/connectors/git/github-v2/pyproject.toml
  • src/ingestion/connectors/git/gitlab/pyproject.toml
  • src/ingestion/connectors/hr-directory/active-directory/pyproject.toml
  • src/ingestion/tests/connectors/pyproject.toml
  • src/ingestion/tools/declarative-connector/Dockerfile
  • src/ingestion/tools/toolbox/Dockerfile

Comment on lines +74 to +75
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow outline =="
wc -l .github/workflows/build-images.yml
rg -n "uses: actions/checkout|persist-credentials|permissions:|GITHUB_TOKEN|docker build|scripts|paths-filter|push" .github/workflows/build-images.yml

echo
echo "== checkout context snippets =="
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/build-images.yml")
lines = p.read_text().splitlines()
for i,line in enumerate(lines, start=1):
    if "actions/checkout" in line:
        start=max(1,i-5); end=min(len(lines),i+12)
        print(f"\n--- context at line {i} ---")
        for j in range(start,end+1):
            print(f"{j:5}: {lines[j-1]}")
PY

Repository: constructorfabric/insight

Length of output: 22530


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant workflow headers and job names =="
sed -n '1,180p' .github/workflows/build-images.yml | sed -n '1,180p'
echo
sed -n '220,270p' .github/workflows/build-images.yml
echo
sed -n '320,375p' .github/workflows/build-images.yml
echo
sed -n '420,475p' .github/workflows/build-images.yml
echo
sed -n '530,580p' .github/workflows/build-images.yml
echo
sed -n '645,690p' .github/workflows/build-images.yml
echo
sed -n '805,850p' .github/workflows/build-images.yml

echo
echo "== changed scripts/files referenced by workflow =="
sed -n '1,20p' .github/workflows/build-images.yml
fd -a '.github/workflows/scripts' .github/workflows || true

Repository: constructorfabric/insight

Length of output: 23204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== .github/workflows/scripts contents =="
find .github/workflows/scripts -type f -print | sort | while IFS= read -r f; do
  echo "--- $f ---"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo
echo "== read-only YAML/action/config script usage check =="
python3 - <<'PY'
from pathlib import Path
import re
wf = Path(".github/workflows/build-images.yml").read_text(linesep="\n").splitlines()
scripts = {}
for root, _, files in sorted([[*(Path(".github/workflows/scripts").iterdir())]] if Path(".github/workflows/scripts").exists() else []):
    pass
# Actually pathlib only dirs
files = sorted(Path(".github/workflows/scripts").glob("*")) if Path(".github/workflows/scripts").exists() else []
for f in files:
    if not f.is_file(): continue
    scripts[f.name] = f.read_text().splitlines()

for key, name in [
    ("discover-image-matrix", "python3 .github/workflows/scripts/discover-image-matrix.py"),
    ("bump-service-appversions", ".github/workflows/scripts/bump-service-appversions.sh"),
    ("bump-descriptor-version", ".github/workflows/scripts/bump-descriptor-version.sh"),
]:
    script = scripts.get(key)
    pattern = "(?:python3 |)(\\.github/workflows/scripts/" + re.escape(key) + ".*|.*" + re.escape(key) + ".*|discover-image-matrix|bump-service-appversions|bump-descriptor-version)"
    matches = []
    for i,line in enumerate(wf,1):
        if key in line:
            matches.append((i,line[:180]))
    print(f"{key}: lines {matches}")
PY

Repository: constructorfabric/insight

Length of output: 12938


Disable checkout credential persistence in the build jobs.

actions/checkout writes GITHUB_TOKEN to .git/config by default. These jobs run workflow scripts or Docker build content before any write-only registry login, so non-push steps should not keep the token in Git. Add persist-credentials: false to the unprotected checkouts at lines 74, 180, 268, 369, 469, 680, and 846. Keep the checkout token persistence at lines 1001 and 1184 only because those steps need the remote token for pushing.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 74-74: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-images.yml around lines 74 - 75, Update the
actions/checkout steps in the build jobs at the specified unprotected locations
to set persist-credentials to false, including the checkout adjacent to
dorny/paths-filter. Leave the checkout steps used for pushing at the later
protected locations unchanged so they retain credential persistence.

Source: Linters/SAST tools

Comment on lines +996 to 999
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
private-key: ${{ secrets.AUTOMATION_APP_PRIVATE_KEY }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(\.github/workflows/build-images.yml|package.json|package-lock.json|pnpm-lock.yaml|yarn.lock)$' || true

echo
echo "Workflow relevant lines around create-github-app-token:"
sed -n '970,1010p' .github/workflows/build-images.yml 2>/dev/null || true
echo
sed -n '1160,1195p' .github/workflows/build-images.yml 2>/dev/null || true

echo
echo "Search for permissions usage in workflow:"
rg -n "permissions:|create-github-app-token|permission-|pull_request|push|contents|permissions" .github/workflows/build-images.yml || true

echo
echo "Search for action in repository (maybe vendored):"
rg -n "create-github-app-token|createGithubAppToken|permissions" .github/workflows/build-images.yml .github/actions 2>/dev/null || true

Repository: constructorfabric/insight

Length of output: 14948


🌐 Web query:

actions/create-github-app-token inputs permission default omitted installs create token

💡 Result:

When using the actions/create-github-app-token GitHub Action, if you omit the permission inputs (the optional permission-* parameters), the generated installation access token defaults to inheriting all permissions currently granted to the GitHub App installation [1][2][3]. In this default state, the token receives the full set of permissions defined in the app's installation configuration [4][5]. Because this can grant more access than may be necessary for a specific workflow task, it is considered a best practice to explicitly define only the required permissions using the permission- prefix followed by the specific permission name (e.g., permission-contents: read) [1][5]. Key details regarding this behavior include: - Inheritance: If no permission-* inputs are provided, the action effectively requests a token with the maximum scope available to that installation [4][6]. - Security Recommendation: GitHub and the action maintainers recommend explicitly listing required permissions to follow the principle of least privilege [1][2][5]. - Validation: If you explicitly set a permission that the GitHub App installation does not possess, the API call to generate the token will fail [6][2][3]. - Implementation: Permissions are parsed from environment variables (prefixed with INPUT_PERMISSION-), normalized, and combined into a request object used when interacting with the GitHub API [4][6]. If you encounter issues when using specific permissions, it is often because the requested subset does not cover the requirements of the workflow tasks, whereas omitting them (defaulting to all) allows the action to succeed by providing broader access [7].

Citations:


Restrict the GitHub App token permissions.

This workflow does not request permission-* inputs from actions/create-github-app-token, so the generated installation token inherits the App installation’s configured permissions. These jobs need repository Contents access for checkout and push. Set permission-contents: write and omit any extra scopes.

Proposed fix
         with:
           app-id: ${{ vars.AUTOMATION_APP_ID }}
           private-key: ${{ secrets.AUTOMATION_APP_PRIVATE_KEY }}
+          permission-contents: write

Also applies to: 1179-1182

🧰 Tools
🪛 zizmor (1.28.0)

[error] 996-996: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-images.yml around lines 996 - 999, Update both
actions/create-github-app-token steps in the workflow to explicitly request only
repository Contents access by adding permission-contents: write alongside the
existing app-id and private-key inputs. Do not add any other permission-*
inputs, preserving the token’s use for checkout and push.

Source: Linters/SAST tools

- name: Trivy fs scan — CRITICAL only (report-only)
# Waivers: `.trivyignore` at the repo root, date-scoped — `CVE-... exp:YYYY-MM-DD # <issue>`.
# Keep `-w /src`; `--ignorefile` exits FATAL when the file is absent.
# `.trivyignore.yaml` must be passed explicitly — the YAML form is not auto-detected.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the adjacent waiver comment.

The preceding comment refers to .trivyignore and date-scoped CVE syntax. This workflow now uses .trivyignore.yaml with a path-scoped AVD-DS-0002 waiver. Keep the comment aligned with the committed file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/trivy.yml at line 48, Update the comment adjacent to the
Trivy ignore-file configuration to describe the committed .trivyignore.yaml file
and its path-scoped AVD-DS-0002 waiver, removing outdated references to
.trivyignore and date-scoped CVE syntax.

Comment thread .github/workflows/trivy.yml Outdated
The three scan steps carried a literal backslash-n where a line continuation
belongs, so the command collapsed onto one line and --ignorefile landed among
the `docker run` flags. Docker rejects it:

    unknown flag: --ignorefile
    Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
    exit code 125

Both trivy jobs fail that way, which also means no SARIF is uploaded and Code
Scanning stops being refreshed. It went unseen because trivy.yml only runs on
main and on pull requests into it; every branch so far targeted
security-hardening, where the workflow reports `skipping`.

Moving the flag after the image and the `fs` subcommand is the whole fix. Also
the first time the .trivyignore.yaml waiver actually takes effect.

Signed-off-by: Gregory Gogin <grigoriy.gogin@constructor.tech>
@Gregory91G
Gregory91G enabled auto-merge August 3, 2026 16:46
@Gregory91G
Gregory91G added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 87bf1e2 Aug 3, 2026
75 of 77 checks passed
@Gregory91G
Gregory91G deleted the security-hardening branch August 3, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants