Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:26-slim@sha256:ffc78385a788964bb3cbab5e434ff79a10bdc25b8ae6db03fe5fe6cb14053c09
FROM node:26-slim@sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- AGENTS.md files ---'
find .. -name AGENTS.md -print
printf '%s\n' '--- root and frontend guidance ---'
for f in AGENTS.md frontend/AGENTS.md; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat "$f"
  fi
done
printf '%s\n' '--- relevant files ---'
sed -n '1,120p' frontend/Dockerfile
sed -n '60,95p' .github/workflows/docker-publish.yml
printf '%s\n' '--- governance references ---'
rg -n -C 3 'OCI_IMAGE_BASE|base_digest|node:26-slim|191ef878|deae974a' backend/tests .github frontend

Repository: ContextualWisdomLab/naruon

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- focused Dockerfile lines ---'
nl -ba frontend/Dockerfile | sed -n '1,22p'
printf '%s\n' '--- focused workflow references ---'
rg -n -C 5 'base_digest|node:26-slim|191ef878|deae974a' .github/workflows/docker-publish.yml
printf '%s\n' '--- focused governance test references ---'
rg -n -C 8 'base_digest|OCI_IMAGE_BASE|node:26-slim|digest|Dockerfile' backend/tests/test_release_governance.py

printf '%s\n' '--- parsed digest consistency ---'
python3 - <<'PY'
from pathlib import Path
import re

paths = ["frontend/Dockerfile", ".github/workflows/docker-publish.yml", "backend/tests/test_release_governance.py"]
for path in paths:
    text = Path(path).read_text()
    digests = sorted(set(re.findall(r"sha256:[0-9a-f]{64}", text)))
    print(path, digests)

dockerfile = Path("frontend/Dockerfile").read_text()
from_digest = re.search(r"^FROM\s+node:26-slim@(sha256:[0-9a-f]{64})$", dockerfile, re.M)
base_digest = re.search(r'^ARG OCI_IMAGE_BASE_DIGEST="(sha256:[0-9a-f]{64})"$', dockerfile, re.M)
base_name = re.search(r'^ARG OCI_IMAGE_BASE_NAME="docker.io/library/node:26-slim@(sha256:[0-9a-f]{64})"$', dockerfile, re.M)
print("FROM digest:", from_digest.group(1) if from_digest else None)
print("OCI_IMAGE_BASE_DIGEST:", base_digest.group(1) if base_digest else None)
print("OCI_IMAGE_BASE_NAME digest:", base_name.group(1) if base_name else None)
PY

Repository: ContextualWisdomLab/naruon

Length of output: 14334


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import json
import re
import urllib.parse
import urllib.request

repo = "library/node"
tag = "26-slim"
digests = [
    "sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a",
    "sha256:191ef878ecb351d68b78219593de18bd8942afd59af59f29960dc4b24805a3f1",
]
token_url = "https://auth.docker.io/token?" + urllib.parse.urlencode({
    "service": "registry.docker.io",
    "scope": f"repository:{repo}:pull",
})
with urllib.request.urlopen(token_url, timeout=20) as response:
    token = json.load(response)["token"]

base = f"https://registry-1.docker.io/v2/{repo}/manifests/"
accept = ", ".join([
    "application/vnd.oci.image.index.v1+json",
    "application/vnd.docker.distribution.manifest.list.v2+json",
    "application/vnd.oci.image.manifest.v1+json",
    "application/vnd.docker.distribution.manifest.v2+json",
])
headers = {"Authorization": f"Bearer {token}", "Accept": accept}

def get_manifest(ref):
    request = urllib.request.Request(base + ref, headers=headers)
    with urllib.request.urlopen(request, timeout=20) as response:
        body = response.read()
        return response.headers.get("Docker-Content-Digest"), response.headers.get("Content-Type"), json.loads(body)

tag_digest, tag_type, tag_manifest = get_manifest(tag)
print("tag", tag, "header_digest", tag_digest, "content_type", tag_type)
print("tag_manifest_digest", tag_manifest.get("digest"))
for digest in digests:
    resolved, content_type, manifest = get_manifest(digest)
    print("ref", digest, "header_digest", resolved, "content_type", content_type)
    print("  mediaType", manifest.get("mediaType"))
    if "manifests" in manifest:
        print("  child_manifests", len(manifest["manifests"]))
        print("  matches_tag_child_set", {
            (item.get("digest"), item.get("platform", {}).get("architecture"), item.get("platform", {}).get("os"))
            for item in manifest["manifests"]
        } == {
            (item.get("digest"), item.get("platform", {}).get("architecture"), item.get("platform", {}).get("os"))
            for item in tag_manifest.get("manifests", [])
        })
    print("  equals_tag_header_digest", digest == tag_digest)
PY

Repository: ContextualWisdomLab/naruon

Length of output: 2451


🌐 Web query:

"sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a" node 26 slim

💡 Result:

The SHA256 digest sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a is a valid image digest associated with the official Node.js Docker image, specifically for the slim and trixie-slim variants [1][2][3]. This digest serves as a content-addressable identifier for these specific image tags on Docker Hub [1][2][3]. As of August 2026, it is commonly referenced in the official Node repository for tags such as node:slim and node:trixie-slim [1][2][3]. To use this specific image version, you can pull it by digest to ensure you are running the exact same bits, regardless of tag updates: docker pull node@sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a

Citations:


🌐 Web query:

"sha256:191ef878ecb351d68b78219593de18bd8942afd59af59f29960dc4b24805a3f1" node 26 slim

💡 Result:

The SHA-256 hash 191ef878ecb351d68b78219593de18bd8942afd59af59f29960dc4b24805a3f1 refers to a specific image layer digest within the official Node.js Docker library, specifically associated with the linux/ppc64le architecture for various tags of the node:slim image [1][2]. It is not a manifest digest for the entirety of a "node 26 slim" image, but rather a specific component layer found in multi-platform image manifests [1][2]. Official Docker images are multi-architecture; pulling node:26-slim will automatically resolve to the manifest digest appropriate for your specific host system's architecture, rather than this specific layer hash [3][4]. If you are attempting to verify an image, you should compare the full image digest (often provided in the Docker pull output or the Docker Hub "Digest" field) rather than individual layer hashes, as layer hashes may vary between architectures or minor build updates [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
token="$(
  curl -ksS --fail \
    'https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/node:pull' |
  python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])'
)"
accept='application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json'
for ref in 26-slim \
  sha256:deae974a69e140f44f434ab29cb519fb5f8fe250fd364b8ca446bd0761acdc6a \
  sha256:191ef878ecb351d68b78219593de18bd8942afd59af59f29960dc4b24805a3f1; do
  echo "### $ref"
  curl -ksS --fail \
    -H "Authorization: Bearer $token" \
    -H "Accept: $accept" \
    -D /tmp/node-manifest-headers \
    "https://registry-1.docker.io/v2/library/node/manifests/$ref" \
    -o /tmp/node-manifest.json
  grep -iE '^(docker-content-digest|content-type):' /tmp/node-manifest-headers || true
  python3 - <<'PY'
import json
from pathlib import Path
m = json.loads(Path("/tmp/node-manifest.json").read_text())
print("mediaType:", m.get("mediaType"))
print("schemaVersion:", m.get("schemaVersion"))
print("manifest_count:", len(m.get("manifests", [])))
for item in m.get("manifests", []):
    platform = item.get("platform", {})
    print(item.get("digest"), platform.get("os"), platform.get("architecture"), platform.get("variant"))
PY
done

Repository: ContextualWisdomLab/naruon

Length of output: 278


Synchronize the base-image digest across all references.

frontend/Dockerfile uses a different digest in FROM than its OCI metadata. Both frontend metadata blocks in .github/workflows/docker-publish.yml use the old digest. Use one intended digest in all references and assert exact equality in the governance test.

🤖 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 `@frontend/Dockerfile` at line 1, Update the frontend Docker base-image digest
in the FROM declaration and both frontend OCI metadata blocks in
docker-publish.yml to one shared intended digest, then update the governance
test to assert exact equality across all references.


ARG OCI_IMAGE_CREATED=""
ARG OCI_IMAGE_AUTHORS="Seongho Bae"
Expand Down Expand Up @@ -69,4 +69,4 @@
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || '3000')).then((response) => { if (!response.ok) process.exit(1); }).catch(() => process.exit(1))"

# Render injects $PORT at runtime
CMD sh -c "exec ./node_modules/.bin/next start --hostname 0.0.0.0 --port ${PORT:-3000}"

Check warning on line 72 in frontend/Dockerfile

View workflow job for this annotation

GitHub Actions / validate frontend image

JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals

JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals More info: https://docs.docker.com/go/dockerfile/rule/json-args-recommended/
Loading