Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ Deeper design: [Developer guide](./docs/guides/DEVELOPER_GUIDE.md), [Architectur
```bash
mise run install # yarn workspaces + agent Python (uv)
mise run build # agent quality + cdk + cli + docs (parallel)
mise run security # secrets, deps, sast, grype, retire, gh-actions, agent
mise run security # secrets, deps, sast, sast:masking, grype, retire, gh-actions, agent
mise run hooks:install # prek git hooks (also runs at end of install)
mise run hooks:run # pre-commit + pre-push locally
```

Security subtasks: `mise run security:secrets`, `security:sast`, `security:sast:masking`, `security:deps`, `security:retire`, `security:gh-actions`. For `security:sast:masking` allowlist intentional fallbacks with an inline `nosemgrep: <rule-id> -- <reason>` comment on the flagged `return` line (or the line immediately above) — the rule anchors on the `return`, so a token placed higher does not bind.

**Claim the security legs one at a time, never in aggregate.** `mise run security` runs its eight legs in the order listed above and stops at the first failure, so "`mise run security` passes" is only ever a statement about the legs that actually ran. This is not a local-only hazard: the weekly `security.yml` cron — the *only* surface that runs the container image scan — invokes the same aggregate, so a red early leg suppresses the later ones there too, and the issue it auto-files names just the first failure. That is how 14 fixable HIGH/CRITICAL findings in the image-bundled `gh` / `uv` / `npm` binaries sat unseen: the container scan is inside the *last* leg, and a red `security:sast` (3rd) or `security:sast:masking` (4th) stops the chain five legs short of it — see [#897](https://github.com/aws-samples/sample-autonomous-cloud-coding-agents/issues/897). Note which leg is red is not stable, so do not memorise one: fixing the earlier one just uncovers the next. PR checks do not compensate: `security-pr.yml` runs only the diff-scoped `security:secrets:range` and `security:sast:masking:range` plus `security:deps` and `security:gh-actions`; whole-repo `security:sast` and `//agent:security` are absent from it, and `build.yml` sets `MISE_DISABLE_TOOLS: "aqua:aquasecurity/trivy,grype,semgrep"` ([#235](https://github.com/aws-samples/sample-autonomous-cloud-coding-agents/issues/235)). So 8/8 green PR checks say nothing about those legs — run them by name before asserting they pass.

Package commands: [cdk/AGENTS.md](./cdk/AGENTS.md), [cli/AGENTS.md](./cli/AGENTS.md), [agent/AGENTS.md](./agent/AGENTS.md), [docs/AGENTS.md](./docs/AGENTS.md).

Run `mise tasks --all` with `MISE_EXPERIMENTAL=1` for the full list. Prefer mise tasks over raw `jest`, `tsc`, or `cdk` for full suites; package guides show targeted `npx jest` / `uv run pytest` for single files.
Expand Down
66 changes: 64 additions & 2 deletions agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
ARG TARGETPLATFORM=linux/arm64
ARG GH_VERSION=2.93.0
# ``gh`` is compiled from source below (``go install …@vX``), so its vendored module
# set is whatever this tag pins — there is no separate dep file to patch, and a
# version bump is the only lever. 2.101.0 clears the nine fixable HIGH/CRITICAL
# findings #897 recorded against 2.93.0: sigstore/rekor 1.5.3 (CVE-2026-48702),
# x/crypto 0.57.0, x/mod 0.41.0, x/net 0.58.0, x/text 0.42.0, grpc 1.83.2. Its
# ``go 1.27.0`` directive is satisfied by the pinned golang:1.27.1 builder below.
# When bumping, confirm by re-scanning the built image, not by reading a changelog.
ARG GH_VERSION=2.101.0

# Every external image is pinned by DIGEST, not by tag.
#
Expand Down Expand Up @@ -93,10 +100,65 @@ RUN npm install -g npm@latest && \
node "${CLAUDE_NPM_ROOT}/install.cjs" && \
claude --version

# Patch npm's OWN bundled dependencies (#897).
#
# ``npm install -g npm@latest`` above does not fix these. npm 12.0.2 — the current
# latest — still ships brace-expansion 5.0.7 (CVE-2026-14257, CVE-2026-69152),
# ip-address 10.2.0 (CVE-2026-69192) and tar 7.5.19 (CVE-2026-73566), each with a
# published fix. So ``npm@latest`` is not a security control here; it was measured,
# not assumed.
#
# The approach used just above for the claude-code tree (``npm --prefix <tree>
# update …``) CANNOT be reused here. npm's published package.json declares
# ``@npmcli/docs@^1.0.0``, a workspace-internal package that is not on the
# registry, and Arborist resolves the whole declared graph before touching
# anything — so every npm-native operation inside npm's own tree dies with E404
# (verified for ``update``, ``update --omit=dev`` and ``install --no-save``).
# npm's bundled node_modules is a build artifact, not an installable project.
#
# Hence a direct tarball overlay. ``npm pack`` fetches through the registry's
# integrity check, and each package is pure JS at a patch/minor bump, so the new
# file set supersedes what it replaces.
#
# The pinned version is a FLOOR, not an assignment: ``npm@latest`` floats, so a
# later npm could bundle something newer, and overwriting blindly would silently
# walk it backwards. npm's own bundled ``semver`` does the comparison and the
# overlay is skipped when the shipped version already satisfies the floor.
#
# Both failure modes are hard-verified rather than left to degrade into a no-op:
# a package missing from the tree fails the build (the layout is npm's internal
# detail and may change), and the post-overlay version is asserted.
RUN NPM_TREE="$(npm root -g)/npm" && \
SEMVER="${NPM_TREE}/node_modules/semver" && \
mkdir -p /var/tmp/npm-cve && cd /var/tmp/npm-cve && \
for pv in brace-expansion:5.0.9 ip-address:10.3.1 tar:7.5.21; do \
name="${pv%:*}"; floor="${pv#*:}"; \
pkg="${NPM_TREE}/node_modules/${name}/package.json"; \
[ -f "${pkg}" ] || { echo "FATAL: ${name} absent from npm's bundled tree; layout changed, re-check #897"; exit 1; }; \
cur="$(node -p "require('${pkg}').version")"; \
if node -e "process.exit(require('${SEMVER}').gte('${cur}','${floor}')?0:1)"; then \
echo "npm bundled dep ${name} ${cur} already satisfies floor ${floor}; left alone"; \
else \
tarball="$(npm pack "${name}@${floor}" --silent)"; \
for dir in $(find "${NPM_TREE}/node_modules" -type d -name "${name}"); do \
tar -xzf "/var/tmp/npm-cve/${tarball}" -C "${dir}" --strip-components=1; \
done; \
got="$(node -p "require('${pkg}').version")"; \
[ "${got}" = "${floor}" ] || { echo "FATAL: ${name} is ${got} after overlay, expected ${floor}"; exit 1; }; \
echo "npm bundled dep ${name} ${cur} -> ${got}"; \
fi; \
done && \
cd / && rm -rf /var/tmp/npm-cve && \
npm --version

# Install uv (fast Python package manager) — pinned by digest, not just version:
# a published tag can be re-pushed, and this COPY sits above the dependency sync,
# so a silent move here invalidates it. See the digest note at the top of the file.
COPY --from=ghcr.io/astral-sh/uv:0.11.14@sha256:1025398289b62de8269e70c45b91ffa37c373f38118d7da036fb8bb8efc85d97 /uv /usr/local/bin/uv
# 0.12.17 vendors quinn-proto 0.11.18, clearing GHSA-4w2j-m93h-cj5j (#897). Read that
# advisory carefully: its "fixed in 0.11.15" is *quinn-proto's* version, not uv's —
# uv 0.11.14 happens to vendor quinn-proto 0.11.14, which makes the two look like one
# number. The floor is therefore a uv release that vendors ≥ 0.11.15, not uv 0.11.15.
COPY --from=ghcr.io/astral-sh/uv:0.12.17@sha256:10787c682e4184e4f290de1171fd4703dc63de99221f10fe1c99002ce7fa9acc /uv /usr/local/bin/uv

# Install Python dependencies via uv. Build context is repo root (set in
# ``cdk/src/stacks/agent.ts``) so source paths are prefixed with ``agent/``.
Expand Down
Loading