Skip to content

normalize public keys - #13

Merged
smecsia merged 1 commit into
mainfrom
fix/public-key-normalize
Oct 13, 2025
Merged

normalize public keys#13
smecsia merged 1 commit into
mainfrom
fix/public-key-normalize

Conversation

@smecsia

@smecsia smecsia commented Oct 7, 2025

Copy link
Copy Markdown
Contributor

No description provided.

@smecsia
smecsia requested a review from Laboratory October 7, 2025 19:17
@smecsia
smecsia merged commit 252c224 into main Oct 13, 2025
2 checks passed
@smecsia
smecsia deleted the fix/public-key-normalize branch October 20, 2025 20:27
Cre-eD added a commit that referenced this pull request May 17, 2026
…ools composites

simple-container-com/actions PR #12 + #13 merged on 2026-05-17, exposing
two consolidated composites the api repo can now reach via `@<sha>`.
Migrate the matching call sites here:

  1. Five `install-sc + install-welder` PAIRS collapse to a single
     `setup-sc-tooling` invocation. Saves five `- name: ...` blocks /
     ten `uses:` lines. Pairs touched:
       - push.yaml (×2)
       - branch-preview.yaml
       - build-staging.yml
       - branch.yaml
     Standalone `install-sc` (branch-preview ×3, push ×1) and the
     `install-welder`-only call in simple-forge.yml stay on the
     individual composites — the new wrapper does not improve them
     and consistency is not worth a contracting diff. The standalone
     composites remain part of the shared ruleset.

  2. Six api-local `./.github/actions/install-attest-tools` refs
     swap to the now-shared
     `simple-container-com/actions/install-attest-tools@<sha>`. Same
     pinned cosign / syft versions (v2.4.1 / v1.16.0) — drop-in
     replacement. The api-local copy at
     `.github/actions/install-attest-tools/action.yml` is removed.

Net diff: -26 LOC, no behavior change at any call site (composite
contracts are identical to the inlined call sequences they replace).
actionlint warning count unchanged vs baseline.

Step C-1 (`sign-and-attest`) is NOT adopted in this commit — the
inline signing blocks in push.yaml / branch-preview.yaml /
build-staging.yml are functionally identical to the composite, but the
swap deserves its own preview-build validation cycle, and that cycle
fits better in the Step C-2 session that also lands the wrapping
`sc-image-release.yml` reusable workflow.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Cre-eD added a commit that referenced this pull request May 17, 2026
simple-container-com/actions PR #13 merged on 2026-05-17, exposing
the `sign-and-attest` composite that encapsulates the four-step
attestation flow (CycloneDX SBOM via syft + cosign keyless sign +
cosign attest cyclonedx + SLSA L3 via actions/attest-build-provenance).
The composite has its own end-to-end self-test on the actions repo
(`self-test-sign-and-attest.yml` — actually pushes an image to ghcr.io
and runs all three verification commands), so the contract is
validated against real Sigstore on every actions-repo PR.

Migrate the matching call sites here. Each migration is a 5-step → 1-step
swap: install-attest-tools + Generate SBOM + Cosign sign + Cosign attest
SBOM + SLSA build provenance all collapse into one `sign-and-attest`
invocation with `soft-fail: 'true'` (matches the prior continue-on-error
default during Phase 2 bake-in).

Files touched:
  - push.yaml (docker-build matrix): 4 images × 5 steps → 1 step
  - branch-preview.yaml (image-build matrix): same
  - build-staging.yml: 2 images × 4-or-5 steps → 2 steps. Kept the
    top-level `install-attest-tools` step here because both composite
    invocations share the runner; the two `sign-and-attest` calls use
    `install-tools: 'false'` so the install is paid once.

Aggregator steps that emit ::warning:: rewrite from per-step
`steps.X.outcome` to the composite's output values
(`steps.signattest.outputs.{sbom,sign,attest,slsa}-outcome`). Same
semantics: warn if any sub-step is not `success` (or `skipped` for the
build-staging case where one of two images may not have been built).

Net diff: −129 LOC across 3 workflows. Behaviour is byte-identical to
the inline blocks the composite replaces — the composite's
implementation is the exact same 4-step sequence the inline blocks
performed, validated by the actions-repo self-test.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Cre-eD added a commit that referenced this pull request May 17, 2026
…Dependabot

Two changes flagged by the codex+gemini meta-review of our "reduce
per-consumer pin maintenance" effort (HARDENING.md "Consumer-side pin
reduction — RESOLVED" section captures the full lesson):

## 1. Align all simple-container-com/actions/* refs to one SHA

The api workflows referenced two different actions-repo SHAs after the
incremental composite migration:

  - bc4a81b (8x notify-telegram, 4x install-sc, 1x install-welder, 4x
    reusable security/semgrep workflows) — pre-#13
  - a374c76 (5x setup-sc-tooling, 4x sign-and-attest, 4x install-
    attest-tools) — post-#13

Both still resolve on actions/main, so functionally equivalent, but
the split adds cognitive load and makes Dependabot's eventual bumps
land as fragmented PRs. Single sed: every `simple-container-com/
actions/*@<sha>` ref now points at the current actions/main HEAD
(`0af5a697f24ea484991660619d0ae42d50343b9d` — the post-#16 merge),
unified.

## 2. Dependabot grouping for simple-container-com/actions/*

The actual fix for the original "21+ Dependabot PRs per actions-repo
release" pain (which composites + sign-and-attest reduced LOC but not
PR-count). The new `simple-container-com-actions` group in
`.github/dependabot.yml` batches every bump matching the patterns

  - simple-container-com/actions
  - simple-container-com/actions/*

into ONE PR per upstream release. Pattern-based groups resolve before
update-type groups, so this is independent of major/minor/patch
classification — security advisories on our own shared actions arrive
as a single reviewable PR. The pre-existing actions-minor-and-patch
group continues to batch THIRD-PARTY action bumps the same way.

## Why both changes together

The meta-review (codex + gemini, round 3) converged on the playbook:

  Step 1 — Dependabot/Renovate grouping FIRST (solve fatigue at the
           bot layer; ~10 LOC config).
  Step 2 — Composites SECOND (when actions are semantically paired
           and the encapsulated logic is shared).
  Step 3 — Reusable workflows ONLY when ≥ 3 consumers AND the
           workflow is self-contained AND the job boundary is stable.

We did Steps 2 + 3 first (composites + the now-abandoned sc-image-
release.yml), which was overbuilt one step too early. This commit
adds Step 1 retroactively. With grouping enabled, the user's
original "less than 9 entries" goal is achieved at the PR level
(one PR per release, regardless of `uses:` count) — not at the
workflow-file level.

## actionlint

No new warnings vs baseline (pre-existing blacksmith / shellcheck
nits in existing run: blocks I didn't touch).

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Cre-eD added a commit that referenced this pull request May 18, 2026
…adges (#263)

## Summary

Three Scorecard sub-10 checks fixed in one bundled PR. Projected delta:
**7.8 → ~9.2** on next rescan.

| Check | Before | After | Why |
|---|---|---|---|
| Token-Permissions | 0/10 | **10/10** | `build-staging.yml` +
`simple-forge.yml` shipped without a top-level `permissions:` block;
both now default to `contents: read`. |
| Pinned-Dependencies | 6/10 | **10/10** | Remaining unpinned action
refs were first-party `simple-container-com/*` on `@main` / `@v1`.
Scorecard treats those identically to third-party. SHA-pinned all of
them with a `# main` / `# v1` trailing comment so Dependabot still
tracks the symbolic ref. |
| README badges | — | **added** | OpenSSF Scorecard + CodeQL + Semgrep +
SLSA L3 + Sigstore + Go Report Card + License at the top of `README.md`.
OpenSSF Best Practices badge waits for the admin-UI submission tracked
in Phase 8. |
| Composite consolidation | — | **adopted** | After
simple-container-com/actions PR #12 + #13 merged on 2026-05-17, three
migrations: (a) five `install-sc + install-welder` PAIRS collapse to
`setup-sc-tooling@<sha>`; (b) six
`./.github/actions/install-attest-tools` refs swap to the now-shared
`simple-container-com/actions/install-attest-tools@<sha>` (api-local
copy deleted); (c) the inline 5-step image signing block (Install attest
tools + SBOM + cosign sign + cosign attest + SLSA provenance) collapses
to one `sign-and-attest@<sha>` call across push.yaml +
branch-preview.yaml + build-staging.yml. −155 LOC, byte-identical
behaviour at every call site. |

## What changed

### Token-Permissions

```diff
+ permissions:
+   contents: read
```

Added to:
- `build-staging.yml` (per-job `id-token: write` + `attestations: write`
for cosign + attest-build-provenance kept intact)
- `simple-forge.yml` (uses a PAT for checkout; no GITHUB_TOKEN write
scope needed)

### Pinned-Dependencies

| Reference | Old | New | Count |
|---|---|---|---|
| `simple-container-com/actions/install-sc` | `@main` |
`@bc4a81b09e04c41c9ff44f1f07d91634ee687f56 # main` | 5 |
| `simple-container-com/actions/install-welder` | `@main` |
`@bc4a81b09e04c41c9ff44f1f07d91634ee687f56 # main` | 4 |
| `simple-container-com/actions/notify-telegram` | `@main` |
`@bc4a81b09e04c41c9ff44f1f07d91634ee687f56 # main` | 4 |
|
`simple-container-com/actions/.github/workflows/{semgrep,semgrep-comment,security-scan,security-scan-comment}.yml`
| `@main` | `@bc4a81b09e04c41c9ff44f1f07d91634ee687f56 # main` | 4 |
| `simple-container-com/forge-action/.github/actions/dockerless` | `@v1`
| `@b85c96d5f597a28e9dc7528b818c5c79e6575ad5 # v1` | 1 |

The 3 remaining
`simple-container-com/api/.github/actions/...@v${VERSION}` refs in
`branch-preview.yaml` are intentional runtime interpolation against the
preview-release tag and can't be SHA-pinned without breaking the
preview-version test harness.

### README badges

Top-of-README block:
```markdown
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/simple-container-com/api/badge)](...)
[![CodeQL](...)](...)
[![Semgrep](...)](...)
[![SLSA Build L3](...)](...)
[![Sigstore signed](...)](...)
[![Go Report Card](...)](...)
[![License: Apache 2.0](...)](...)
```


### Composite consolidation (added after #12 + #13 merged)

Now that `simple-container-com/actions/setup-sc-tooling` and
`simple-container-com/actions/install-attest-tools` are on
`actions/main`,
the matching call sites in this repo migrate:

| Migration | Before | After | Sites |
|---|---|---|---|
| `install-sc + install-welder` pair collapse | 2 sequential `uses:` per
pair | one `setup-sc-tooling` call | push.yaml (×2),
branch-preview.yaml, build-staging.yml, branch.yaml |
| api-local `install-attest-tools` → shared | `uses:
./.github/actions/install-attest-tools` | `uses:
simple-container-com/actions/install-attest-tools@<sha> # main` |
push.yaml (×2), branch-preview.yaml (×2), build-staging.yml,
verify-attestations.yml |
| api-local action removed |
`.github/actions/install-attest-tools/action.yml` (35 LOC) | deleted | —
|

Net diff: −26 LOC, no functional change at any call site. Pinned cosign
(v2.4.1) + syft (v1.16.0) versions are identical to the api-local
copy — drop-in replacement.

**Step C-1 (`sign-and-attest`) is NOT adopted here.** The inline
signing blocks in push.yaml / branch-preview.yaml / build-staging.yml
are functionally identical to the new composite, but the swap deserves
its own preview-build validation cycle. That cycle fits better in the
follow-up session that also lands the wrapping
`sc-image-release.yml` reusable workflow (Step C-2 in HARDENING.md).


### sign-and-attest adoption (the image signing flow)

The actions-repo `sign-and-attest` composite encapsulates the exact
5-step
attestation sequence each consumer build workflow inlined per-image:

| Step (was) | Composite invocation (now) |
|---|---|
| Install attestation tools (cosign + syft) | (handled by composite via
`install-tools: 'true'` default) |
| Generate CycloneDX SBOM via `syft scan registry:<ref>` | (handled) |
| `cosign sign --yes <ref>` | (handled) |
| `cosign attest --yes --predicate sbom-*.cdx.json --type cyclonedx
<ref>` | (handled) |
| `actions/attest-build-provenance@v4` | (handled, with
`push-to-registry: false` preserved) |

Aggregator steps that emit `::warning::` rewrite from per-step
`steps.X.outcome`
to the composite's outputs
(`steps.signattest.outputs.{sbom,sign,attest,slsa}-outcome`).
Same semantics: warn if any sub-step is not `success` (or `skipped`).

Migration sites:

- **push.yaml** (docker-build matrix): 4 images × 5 steps → 1 step per
image
- **branch-preview.yaml** (image-build matrix): same
- **build-staging.yml**: 2 images. Kept the top-level
`install-attest-tools` step
(both composite invocations share the runner; the two `sign-and-attest`
calls
  use `install-tools: 'false'` so the install is paid once).

The composite has its own real end-to-end self-test in the actions repo
(`self-test-sign-and-attest.yml` — pushes an image to ghcr.io and runs
all
three consumer verification commands: `cosign verify`,
`cosign verify-attestation --type cyclonedx`, `gh attestation verify`).
That validated the contract against real Sigstore on every actions-repo
PR
since #13 merged, so behaviour parity here is high-confidence.

## Out of scope (follow-up PRs)

- **Vulnerabilities (8/10)** — 2 remaining advisories are the documented
`aws-sdk-go v1 s3crypto` FPs. Closes via the v1 → v2 migration tracked
in HARDENING.md (3 `.go` files in `pkg/clouds/{pulumi/,}aws/`).
- **Fuzzing (0/10)** — Go fuzz tests on `pkg/security/cache.go` HMAC
parse path. Separate small PR.
- **OpenSSF Best Practices badge** — admin-UI submission at
https://www.bestpractices.dev/. Self-attest ~67 questions for Passing →
+33 for Silver. Roughly 75 min total, gated by the first signed prod
release landing on dist (Phase 8 trigger checklist).

## Test plan

- [x] `actionlint .github/workflows/*.{yml,yaml}` — clean
- [x] Diff stays surgical (only `permissions:` blocks + `@<sha> # <ref>`
swaps + README header)
- [ ] After merge: Scorecard re-runs on its daily cron; expect
Token-Permissions 10/10 + Pinned-Deps 10/10 + overall ~9.2
- [ ] After merge: README badges render correctly on
github.com/simple-container-com/api

---------

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Cre-eD added a commit that referenced this pull request Aug 22, 2026
…IGH live in github-actions:latest) (#388)

## Summary

`simplecontainer/github-actions:latest` ships **python3 3.14.5-r0** — 12
HIGH, 32 MEDIUM, 4 LOW across `python3` / `pyc` / `python3-pyc` /
`python3-pycache-pyc0` — while Alpine `v3.24/main` has served
`3.14.7-r1` for a while. Our own binary in that image scans **0**; every
finding is the base OS layer.

The cause is the **build cache**, not a dependency version, so #387
could not have caught it.

## Root cause

Release, staging and preview builds all pass `cache-from: type=gha`. The
final stage's base is digest-pinned, and its `apk update && apk upgrade
--no-cache && apk add …` RUN string never changes. So that layer's cache
key is **permanently stable** — the upgrade has not actually executed
since the first ever build of that scope. Straight from the release log
for `076224a`:

```
--cache-from type=gha,scope=prod-github-actions --cache-to type=gha,mode=max,scope=prod-github-actions
#13 [stage-1 2/8] RUN apk update && apk upgrade --no-cache     && apk add --no-cache ca-certificates git openssh-client curl jq bash python3 aws-cli ...
#13 CACHED
```

Confirmed from inside the published image — the package is available, we
just never take it:

```
$ docker run --rm --entrypoint sh simplecontainer/github-actions:latest -c 'apk update -q; apk policy python3'
python3 policy:
  3.14.5-r0:
    lib/apk/db/installed
  3.14.7-r1:
    https://dl-cdn.alpinelinux.org/alpine/v3.24/main
```

`--no-cache` on the apk line is unrelated and easy to misread as
protection: it governs **apk's own index cache**, not Docker layers.

`caddy` and `cloud-helpers` currently look healthy only **by accident**
— #387 changed instructions upstream of their upgrade layers (a new base
digest, a new `RUN`), which busted the chain. `kubectl` was `CACHED` too
and is clean only because its package set happens to contain nothing
vulnerable. Nothing about that is a property we should rely on.

## Fix

Name the final stage `runtime` in all five Dockerfiles, and pass
`no-cache-filters: runtime` to `docker/build-push-action` in
`push.yaml`, `build-staging.yml` (both steps) and `branch-preview.yaml`.

Scoped to the final stage **deliberately**. The expensive work — the
SHA-256-verified Pulumi and gcloud downloads, `gcloud components
install`, and the slimming pass — lives in `builder` and keeps its
cache. This was the open question ("isn't the cache higher priority?"),
so it was measured rather than assumed, on `github-actions.Dockerfile`
with a warm builder:

| build | wall time | python3 | OS findings |
|---|---|---|---|
| fully cached (today's behaviour) | **7.3 s** | 3.14.5-r0 | 48 |
| `--no-cache-filter runtime` | **41.1 s** | **3.14.7-r1** | **0** |

**+34 s per image.** The trust-tier cache scoping added in the earlier
hardening work is untouched — `cache-from` / `cache-to` and the `prod-*`
/ `staging-*` / `preview-*` separation are unchanged, so a lower-trust
build still cannot write a scope a release build reads.

## Verification

All five images built locally with `--no-cache-filter runtime`, then
scanned:

| image | OS-package findings | total | remaining findings are |
|---|---|---|---|
| `github-actions` | **0** (was 48) | 17 | `gcloud-crc32c` +
`gke-gcloud-auth-plugin` — Google's binaries |
| `github-actions-staging` | **0** | 17 | same |
| `cloud-helpers` | **0** | **0** | — |
| `caddy` | **0** | 1 | `cel-go` — needs an upstream Caddy change |
| `kubectl` | **0** | 17 | upstream `kubectl` binary |

Every remaining finding is an upstream binary we do not compile, each
already documented in #387. In-Dockerfile smoke tests (`caddy version`,
plugin greps, `pulumi version`, `gcloud version`, gke-auth-plugin
presence, `aws --version`, `sc` symlink, `test -x /cloud-helpers`) all
pass under the filter. YAML parse-checked on all three workflows.

## Why this matters beyond python3

`git`, `openssh-client`, `curl`, `libssl3`, `libcrypto3` and `aws-cli`
sit in the **same frozen layer**. The next advisory in any of them would
have been just as invisible — in the image that runs every consumer's
deploy while holding their cloud credentials. The python3 CVEs
themselves are mostly DoS plus two `tarfile` extraction-filter bypasses
(`CVE-2026-11940`, `CVE-2026-4360`) that need an attacker-controlled
archive, and Python's inputs here are gcloud's authenticated Google API
traffic — so live exploitability is low. **The reason to fix is that the
patching mechanism was dead, not the severity of what it happened to be
hiding this month.**

## Not switching to distroless / scratch

Considered and rejected for these images, on evidence rather than
preference:

```
$ head -1 $(command -v aws)            -> #!/usr/bin/python3
$ head -1 /opt/google-cloud-sdk/bin/gcloud -> #!/bin/sh      (needs CLOUDSDK_PYTHON)
$ grep -oE '"(gcloud|aws|git|ssh|bash|sh|pulumi)"' -r pkg/  -> bash gcloud git pulumi sh ssh
```

`aws` is a Python script, `gcloud` is a shell script that needs a Python
interpreter, and SC shells out to `bash` / `git` / `ssh` / `pulumi` /
`gcloud` through Pulumi `local.Command` (`aws s3 sync` in the
static-website template, `gcloud auth configure-docker` in the GKE
stack). A shell-less, Python-less base cannot run this image — its
entire purpose is having those tools on `PATH`. `distroless/python3`
would only trade an Alpine base we *can* patch for a Debian base we
can't, minus gcloud, git and ssh.

Scratch **is** worth considering for `cloud-helpers` (a single static Go
binary, already at 0 findings — the gain would be structural immunity to
base drift rather than a fix) and possibly `caddy`. Both are separate
changes and do not belong in a cache fix.

## Test plan

- `docker build --no-cache-filter runtime` for all five Dockerfiles:
build **OK**, in-Dockerfile smoke tests pass, 0 OS-package findings
each.
- Timing measured with a warm builder cache to confirm the builder stage
is unaffected: 7.3 s → 41.1 s.
- `yaml.safe_load` on `push.yaml`, `build-staging.yml`,
`branch-preview.yaml`.
- No Go, `go.mod` or test changes — this is Dockerfile stage naming plus
one workflow input.
- The real proof is the next release:
`simplecontainer/github-actions:latest` should come out with `python3
3.14.7-r1` and 0 alpine findings.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants