Remediate RC6 container scan findings
This commit is contained in:
@@ -101,4 +101,18 @@ def test_release_image_uses_locked_non_ai_dependencies_and_npm_ci() -> None:
|
||||
assert "pip install --no-cache-dir --require-hashes -r requirements-runtime.lock" in dockerfile
|
||||
assert "ARG GEOINTEL_ULTRALYTICS_VERSION=8.4.99" in dockerfile
|
||||
assert '"ultralytics==$GEOINTEL_ULTRALYTICS_VERSION"' in dockerfile
|
||||
assert "ARG GEOINTEL_SETUPTOOLS_VERSION=83.0.0" in dockerfile
|
||||
assert "ARG GEOINTEL_WHEEL_VERSION=0.47.0" in dockerfile
|
||||
assert "COPY deploy/unraid/gosu-setpriv /usr/local/bin/gosu" in dockerfile
|
||||
assert "&& pip check" in dockerfile
|
||||
|
||||
|
||||
def test_gosu_compatibility_wrapper_uses_exec_and_setpriv() -> None:
|
||||
wrapper = read("deploy/unraid/gosu-setpriv")
|
||||
readiness = read("scripts/run_readiness_check.sh")
|
||||
|
||||
assert "exec setpriv" in wrapper
|
||||
assert '--reuid="$target_user"' in wrapper
|
||||
assert '--regid="$target_user"' in wrapper
|
||||
assert "--init-groups" in wrapper
|
||||
assert "bash -n deploy/unraid/gosu-setpriv" in readiness
|
||||
|
||||
@@ -13,6 +13,8 @@ ARG GEOINTEL_TORCH_INDEX_URL=https://download.pytorch.org/whl/cpu
|
||||
ARG GEOINTEL_TORCH_VERSION=2.13.0
|
||||
ARG GEOINTEL_TORCHVISION_VERSION=0.28.0
|
||||
ARG GEOINTEL_ULTRALYTICS_VERSION=8.4.99
|
||||
ARG GEOINTEL_SETUPTOOLS_VERSION=83.0.0
|
||||
ARG GEOINTEL_WHEEL_VERSION=0.47.0
|
||||
|
||||
ENV GEOINTEL_ENV=production \
|
||||
GEOINTEL_API_PREFIX=/api/v1 \
|
||||
@@ -48,6 +50,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY deploy/unraid/gosu-setpriv /usr/local/bin/gosu
|
||||
COPY backend/pyproject.toml /app/
|
||||
COPY backend/requirements-runtime.lock /app/
|
||||
COPY backend/app/__init__.py /app/app/__init__.py
|
||||
@@ -63,6 +66,9 @@ RUN printf '# GeoIntel backend package metadata\n' > /app/README.md \
|
||||
&& pip install --no-cache-dir \
|
||||
"ultralytics==$GEOINTEL_ULTRALYTICS_VERSION"; \
|
||||
fi \
|
||||
&& pip install --no-cache-dir \
|
||||
"setuptools==$GEOINTEL_SETUPTOOLS_VERSION" \
|
||||
"wheel==$GEOINTEL_WHEEL_VERSION" \
|
||||
&& pip check
|
||||
|
||||
COPY backend/ /app/
|
||||
@@ -139,7 +145,7 @@ COPY deploy/unraid/nginx-all-in-one.conf /etc/nginx/conf.d/default.conf
|
||||
COPY deploy/unraid/all-in-one-start.sh /usr/local/bin/geointel-all-in-one-start
|
||||
COPY --from=frontend-build /frontend/dist/ /usr/share/nginx/html/
|
||||
|
||||
RUN chmod +x /usr/local/bin/geointel-all-in-one-start \
|
||||
RUN chmod +x /usr/local/bin/geointel-all-in-one-start /usr/local/bin/gosu \
|
||||
&& chmod +x \
|
||||
/app/scripts/train_operator_yolo_detector.sh \
|
||||
/app/scripts/verify_real_data_detection_qa_workflow.sh \
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "usage: gosu user command [args...]" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
target_user="$1"
|
||||
shift
|
||||
exec setpriv \
|
||||
--reuid="$target_user" \
|
||||
--regid="$target_user" \
|
||||
--init-groups \
|
||||
"$@"
|
||||
@@ -73,12 +73,15 @@ The dependency job:
|
||||
- publishes both unfiltered and policy-filtered Python JSON reports plus the
|
||||
npm JSON report, including on failure.
|
||||
|
||||
The only current Python exceptions are the Starlette 2026 advisories recorded
|
||||
The only current Python/container exceptions are the Starlette 2026 advisories recorded
|
||||
in `security/pip-audit-exceptions.json`. FastAPI 0.139.2 still constrains
|
||||
Starlette below 0.53 while patched releases begin at 1.x. GeoIntel applies
|
||||
request-target, form-content, route-class and Linux-runtime compensating
|
||||
controls. The exception file has a mandatory review date; readiness and CI
|
||||
fail automatically after it expires. New advisories are never auto-ignored.
|
||||
The all-in-one image replaces the Go-based base-image `gosu` helper with a
|
||||
small `setpriv` exec wrapper and upgrades packaged setuptools/wheel metadata;
|
||||
these scanner findings are fixed rather than excepted.
|
||||
|
||||
The container job builds a non-AI all-in-one image and uses digest-pinned
|
||||
scanner images:
|
||||
|
||||
@@ -118,6 +118,7 @@ bash -n scripts/deploy_tower.sh
|
||||
bash -n scripts/verify_release_fresh_install.sh
|
||||
bash -n scripts/verify_release_upgrade_smoke.sh
|
||||
bash -n deploy/unraid/all-in-one-start.sh
|
||||
bash -n deploy/unraid/gosu-setpriv
|
||||
bash -n deploy/unraid/run-dockerman-container.sh
|
||||
bash -n deploy/unraid/deploy-release.sh
|
||||
bash -n deploy/unraid/rollback-dockerman-container.sh
|
||||
|
||||
@@ -6,6 +6,9 @@ TARGET_IMAGE="${1:-geointel-ci:local}"
|
||||
OUTPUT="${2:-artifacts/geointel-container-vulnerabilities.json}"
|
||||
TRIVY_IMAGE="aquasec/trivy:0.70.0@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e"
|
||||
CACHE_DIR="${GEOINTEL_TRIVY_CACHE:-$ROOT/.cache/trivy}"
|
||||
PYTHON_CMD="${PYTHON_BIN:-python3}"
|
||||
IGNORE_FILE="$(mktemp)"
|
||||
trap 'rm -f "$IGNORE_FILE"' EXIT
|
||||
|
||||
case "$OUTPUT" in
|
||||
/*|*..*)
|
||||
@@ -16,6 +19,9 @@ esac
|
||||
|
||||
docker image inspect "$TARGET_IMAGE" >/dev/null
|
||||
mkdir -p "$ROOT/$(dirname "$OUTPUT")" "$CACHE_DIR"
|
||||
"$PYTHON_CMD" "$ROOT/scripts/verify_security_exceptions.py"
|
||||
"$PYTHON_CMD" "$ROOT/scripts/verify_security_exceptions.py" \
|
||||
--print-container-ids > "$IGNORE_FILE"
|
||||
|
||||
# Keep the complete report, including vulnerabilities without an available fix.
|
||||
docker run --rm \
|
||||
@@ -42,6 +48,7 @@ docker run --rm \
|
||||
--timeout 20m \
|
||||
--skip-version-check \
|
||||
--ignore-unfixed \
|
||||
--ignorefile "$IGNORE_FILE" \
|
||||
--severity HIGH,CRITICAL \
|
||||
--exit-code 1 \
|
||||
"$TARGET_IMAGE"
|
||||
|
||||
@@ -40,12 +40,23 @@ def load_and_validate() -> tuple[dict[str, object], list[str]]:
|
||||
if not isinstance(item, dict) or len(str(item.get("reason", ""))) < 30:
|
||||
errors.append("every advisory requires a specific reason")
|
||||
break
|
||||
aliases = [
|
||||
str(alias)
|
||||
for item in advisories
|
||||
if isinstance(item, dict)
|
||||
for alias in item.get("aliases", [])
|
||||
]
|
||||
if len(aliases) != len(set(aliases)) or any(
|
||||
not alias.startswith("CVE-") for alias in aliases
|
||||
):
|
||||
errors.append("container aliases must be unique CVE identifiers")
|
||||
return payload, errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--print-ids", action="store_true")
|
||||
parser.add_argument("--print-container-ids", action="store_true")
|
||||
args = parser.parse_args()
|
||||
payload, errors = load_and_validate()
|
||||
if errors:
|
||||
@@ -55,6 +66,10 @@ def main() -> int:
|
||||
if args.print_ids:
|
||||
for item in payload["advisories"]:
|
||||
print(item["id"])
|
||||
elif args.print_container_ids:
|
||||
for item in payload["advisories"]:
|
||||
for alias in item.get("aliases", []):
|
||||
print(alias)
|
||||
else:
|
||||
print(
|
||||
"Dependency exceptions valid through "
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
{
|
||||
"id": "PYSEC-2026-249",
|
||||
"aliases": ["CVE-2026-54283"],
|
||||
"reason": "GeoIntel rejects application/x-www-form-urlencoded before Starlette form parsing."
|
||||
},
|
||||
{
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
{
|
||||
"id": "PYSEC-2026-2281",
|
||||
"aliases": ["CVE-2026-48818"],
|
||||
"reason": "The supported all-in-one production runtime is Linux; the advisory affects Windows StaticFiles."
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user