Harden service-repo workflows: pin actions, fix Semgrep findings, add docs#40
Merged
Conversation
… docs Resolves the 26 blocking Semgrep SAST findings that the injected vuln-scanner-pr.yml sast-check job reports against the workflow templates this module ships (surfaced in aws-service-infrahouse-app PR #25). Workflow templates: - Pin every third-party action to a full 40-char commit SHA with a # vN comment (github-actions-mutable-action-tag). trufflehog@main, a mutable branch ref, is pinned to v3.95.8. Same-repo reusable-workflow calls stay unpinned. - vuln-scanner-pr.yml: move GITHUB_TOKEN out of the workflow-level env into the two steps that use it (gha-workflow-env-secret). - terraform-drift-wrapper.yml: drop `secrets: inherit` (secrets-inherit). The called workflow only uses the automatic GITHUB_TOKEN, which is always available to reusable workflows and cannot be listed in a secrets: map. - Render environment matrices as YAML block sequences instead of jsonencode(...) flow arrays so output passes yamllint's commas rule; add the missing truthy disable on the wrapper's on: key. Keep the pins current: - Add renovate.json (service-repo previously injected none) with helpers:pinGitHubActionDigests, dashboard-approval gated, no automerge. Docs: - Ship .github/README.md into each service repo (repo-README.md) explaining the managed workflows and renovate.json, since renovate.json cannot carry a managed-by banner comment. - Add a maintainer-facing templates/README.md resource map. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
infrahouse8
previously approved these changes
Jul 4, 2026
State
|
| Success | 🟢 Add | 🟡 Change | Destroy |
|---|---|---|---|
| ✅ | 2 | 6 | 0 |
Affected resources by action
| Action | Resources |
|---|---|
| 🟢 | module.aws_service_infrahouse_app.github_repository_file.renovate[0] |
| 🟢 | module.aws_service_infrahouse_app.github_repository_file.repo_readme[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.secrets_scanner[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.terraform_cd[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.terraform_ci[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.terraform_drift[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.terraform_drift_wrapper[0] |
| 🟡 | module.aws_service_infrahouse_app.github_repository_file.vuln_scanner[0] |
STDOUT
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
~ update in-place
Terraform will perform the following actions:
# module.aws_service_infrahouse_app.github_repository_file.renovate[0] will be created
+ resource "github_repository_file" "renovate" {
+ autocreate_branch_source_sha = (known after apply)
+ branch = (known after apply)
+ commit_message = "Add renovate.json"
+ commit_sha = (known after apply)
+ content = jsonencode(
{
+ "$schema" = "https://docs.renovatebot.com/renovate-schema.json"
+ dependencyDashboardApproval = true
+ extends = [
+ "config:recommended",
+ "helpers:pinGitHubActionDigests",
]
+ packageRules = [
+ {
+ automerge = false
+ matchManagers = [
+ "github-actions",
]
},
]
+ prConcurrentLimit = 1
+ rebaseWhen = "conflicted"
}
)
+ file = "renovate.json"
+ id = (known after apply)
+ overwrite_on_create = true
+ ref = (known after apply)
+ repository = "aws-service-infrahouse-app"
+ repository_id = (known after apply)
+ sha = (known after apply)
}
# module.aws_service_infrahouse_app.github_repository_file.repo_readme[0] will be created
+ resource "github_repository_file" "repo_readme" {
+ autocreate_branch_source_sha = (known after apply)
+ branch = (known after apply)
+ commit_message = "Add .github/README.md documenting managed CI/CD"
+ commit_sha = (known after apply)
+ content = <<-EOT
<!--
This file is managed by Terraform in the github-control repository.
Do not edit it here — changes are overwritten on the next apply.
To change it, open a PR against https://github.com/infrahouse/github-control
(modules/service-repo/templates/repo-README.md).
-->
# CI/CD & automation (managed)
This repository's `.github/workflows/` and `renovate.json` are generated and kept in sync by
[github-control](https://github.com/infrahouse/github-control) through the `service-repo` Terraform
module. **Do not edit those files here** — any change is reverted on the next `terraform apply`. To
change how CI/CD behaves, open a PR against github-control.
## Workflows
### `terraform-CI.yml` — plan on every PR
Runs on `pull_request`. For each environment it runs `terraform plan`, posts the plan as a PR comment
(`ih-plan publish`), and uploads the plan file to the state bucket (`ih-plan upload`) so the exact
reviewed plan can be applied after merge.
### `terraform-CD.yml` — apply on merge
Runs when a PR is merged. For each environment it downloads the plan CI stored, applies it, removes
the stored plan, then runs `make smoke`. Environments deploy in the order set by each environment's
`deploy_order`.
### `terraform-drift.yml` — reusable drift check
A `workflow_call` reusable workflow. Runs `terraform plan -detailed-exitcode` for one environment; if
drift is detected it opens a `config-drift`-labeled PR to reconcile.
### `terraform-drift-wrapper.yml` — scheduled drift sweep
Runs daily (and on demand via `workflow_dispatch`). Calls `terraform-drift.yml` for every environment
and posts to the `#infra` Slack channel on failure.
### `secrets-scanner.yml` — leaked-secret scan
Runs on PRs and the merge queue. Uses TruffleHog to scan the diff for verified secrets.
### `vuln-scanner-pr.yml` — dependency + SAST scan
Runs on PRs and the merge queue. One job runs OSV-Scanner for dependency CVEs; another runs Semgrep
SAST (`semgrep scan --error`), which blocks the PR on findings.
## `renovate.json`
Configures [Renovate](https://docs.renovatebot.com/) for this repo. Every GitHub Action in the
workflows above is pinned to a full commit SHA (with a trailing `# vN` comment) to prevent
supply-chain attacks from silently repointed tags or branches. Renovate's
`helpers:pinGitHubActionDigests` preset keeps those pins current — it opens a PR bumping the SHA and
comment when a new version ships. Updates are queued on the Dependency Dashboard for approval
(`dependencyDashboardApproval`) and never automerge.
`renovate.json` is strict JSON, so it carries no "managed by Terraform" banner comment, but it is
managed exactly like the workflows — edit it in github-control, not here.
EOT
+ file = ".github/README.md"
+ id = (known after apply)
+ overwrite_on_create = true
+ ref = (known after apply)
+ repository = "aws-service-infrahouse-app"
+ repository_id = (known after apply)
+ sha = (known after apply)
}
# module.aws_service_infrahouse_app.github_repository_file.secrets_scanner[0] will be updated in-place
~ resource "github_repository_file" "secrets_scanner" {
~ content = <<-EOT
# This file is managed by Terraform in github-control repository
# Do not edit this file, all changes will be overwritten
---
name: Leaked Secrets Scan
on: # yamllint disable-line rule:truthy
pull_request:
merge_group:
branches: [main]
jobs:
TruffleHog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: TruffleHog OSS
- uses: trufflesecurity/trufflehog@main
+ uses: trufflesecurity/trufflehog@00155c9dc586f34d189adc83d3ac2698c2ec551f # v3.95.8
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified
EOT
id = "aws-service-infrahouse-app:.github/workflows/secrets-scanner.yml:main"
# (9 unchanged attributes hidden)
}
# module.aws_service_infrahouse_app.github_repository_file.terraform_cd[0] will be updated in-place
~ resource "github_repository_file" "terraform_cd" {
~ content = <<-EOT
---
name: "Terraform CD"
on: # yamllint disable-line rule:truthy
pull_request:
types:
- "closed"
permissions:
id-token: "write"
contents: "read"
concurrency:
group: "terraform"
cancel-in-progress: false
jobs:
deploy-order-0:
if: "github.event.pull_request.merged"
strategy:
fail-fast: false
matrix:
- env: ["sandbox"]
+ env:
+ - "sandbox"
name: "Terraform Apply ${{ matrix.env }}"
runs-on: ubuntu-24.04
environment: "live-${{ matrix.env }}"
timeout-minutes: 60
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
TF_VAR_environment: "${{ matrix.env }}"
defaults:
run:
shell: "bash"
steps:
- name: "Checkout"
- uses: "actions/checkout@v6"
+ uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6
- name: "Extract Variables"
id: "extract_vars"
run: |
REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
- name: "Configure AWS Credentials"
- uses: "aws-actions/configure-aws-credentials@v6"
+ uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
role-session-name: "github-actions-${{ matrix.env }}"
aws-region: "${{ steps.extract_vars.outputs.REGION }}"
- name: "Set Terraform version"
id: "terraform_version"
run: |
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
- name: "Setup Terraform"
- uses: "hashicorp/setup-terraform@v4"
+ uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
- name: "Set up Python"
- uses: "actions/setup-python@v6"
+ uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"
- name: "Setup Python Environment"
working-directory: "environments/${{ matrix.env }}"
run: |
make bootstrap
- name: "Download Terraform Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
ih-plan \
--aws-assume-role-arn \
${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
download \
plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
tf.plan
- name: "Terraform Init"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform init -input=false
- name: "Terraform Validate"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform validate -no-color
- name: "Terraform Apply"
working-directory: "environments/${{ matrix.env }}"
run: |
make apply
- name: "Remove Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
ih-plan \
--aws-assume-role-arn \
${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
remove \
plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan
- name: "Smoke Test"
working-directory: "environments/${{ matrix.env }}"
run: |
make smoke ENV=${{ matrix.env }}
deploy-order-1:
if: "github.event.pull_request.merged"
needs: ["deploy-order-0"]
strategy:
fail-fast: false
matrix:
- env: ["production"]
+ env:
+ - "production"
name: "Terraform Apply ${{ matrix.env }}"
runs-on: ubuntu-24.04
environment: "live-${{ matrix.env }}"
timeout-minutes: 60
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
TF_VAR_environment: "${{ matrix.env }}"
defaults:
run:
shell: "bash"
steps:
- name: "Checkout"
- uses: "actions/checkout@v6"
+ uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6
- name: "Extract Variables"
id: "extract_vars"
run: |
REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
- name: "Configure AWS Credentials"
- uses: "aws-actions/configure-aws-credentials@v6"
+ uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
role-session-name: "github-actions-${{ matrix.env }}"
aws-region: "${{ steps.extract_vars.outputs.REGION }}"
- name: "Set Terraform version"
id: "terraform_version"
run: |
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
- name: "Setup Terraform"
- uses: "hashicorp/setup-terraform@v4"
+ uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
- name: "Set up Python"
- uses: "actions/setup-python@v6"
+ uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"
- name: "Setup Python Environment"
working-directory: "environments/${{ matrix.env }}"
run: |
make bootstrap
- name: "Download Terraform Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
ih-plan \
--aws-assume-role-arn \
${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
download \
plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
tf.plan
- name: "Terraform Init"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform init -input=false
- name: "Terraform Validate"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform validate -no-color
- name: "Terraform Apply"
working-directory: "environments/${{ matrix.env }}"
run: |
make apply
- name: "Remove Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
ih-plan \
--aws-assume-role-arn \
${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
remove \
plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan
- name: "Smoke Test"
working-directory: "environments/${{ matrix.env }}"
run: |
make smoke ENV=${{ matrix.env }}
EOT
id = "aws-service-infrahouse-app:.github/workflows/terraform-CD.yml:main"
# (9 unchanged attributes hidden)
}
# module.aws_service_infrahouse_app.github_repository_file.terraform_ci[0] will be updated in-place
~ resource "github_repository_file" "terraform_ci" {
~ content = <<-EOT
---
name: "Terraform CI"
on: # yamllint disable-line rule:truthy
pull_request:
permissions:
id-token: "write"
contents: "read"
pull-requests: "write"
concurrency:
group: "terraform"
cancel-in-progress: false
jobs:
plan:
strategy:
fail-fast: false
matrix:
- env: ["production","sandbox"]
+ env:
+ - "production"
+ - "sandbox"
name: "Terraform Plan ${{ matrix.env }}"
runs-on: ubuntu-24.04
environment: "continuous-integration-${{ matrix.env }}"
timeout-minutes: 15
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
TF_VAR_environment: "${{ matrix.env }}"
defaults:
run:
shell: "bash"
steps:
- name: "Checkout"
- uses: "actions/checkout@v6"
+ uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6
- name: "Extract Variables"
id: "extract_vars"
run: |
REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
- name: "Configure AWS Credentials"
- uses: "aws-actions/configure-aws-credentials@v6"
+ uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
role-session-name: "github-actions-${{ matrix.env }}"
aws-region: "${{ steps.extract_vars.outputs.REGION }}"
- name: "Set Terraform version"
id: "terraform_version"
run: |
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
- name: "Setup Terraform"
- uses: "hashicorp/setup-terraform@v4"
+ uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
- name: "Set up Python"
- uses: "actions/setup-python@v6"
+ uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"
- name: "Setup Python Environment"
working-directory: "environments/${{ matrix.env }}"
run: |
make bootstrap
- name: "Code Style Check"
working-directory: "environments/${{ matrix.env }}"
run: |
make lint
- name: "Terraform Init"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform init -input=false
- name: "Terraform Validate"
working-directory: "environments/${{ matrix.env }}"
run: |
terraform validate -no-color
- name: "Terraform Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
make plan
ih-plan publish \
${{ github.repository }} \
${{ github.event.pull_request.number }} \
plan.stdout plan.stderr
- name: "Upload Terraform Plan"
working-directory: "environments/${{ matrix.env }}"
run: |
ih-plan \
--aws-assume-role-arn \
${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
upload \
--key-name=plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
tf.plan
EOT
id = "aws-service-infrahouse-app:.github/workflows/terraform-CI.yml:main"
# (9 unchanged attributes hidden)
}
# module.aws_service_infrahouse_app.github_repository_file.terraform_drift[0] will be updated in-place
~ resource "github_repository_file" "terraform_drift" {
~ content = <<-EOT
---
name: "Terraform Drift Detection"
on: # yamllint disable-line rule:truthy
workflow_call:
inputs:
env:
type: "string"
required: true
permissions:
id-token: "write" # This is required for requesting the JWT
contents: "write"
pull-requests: "write"
issues: "write" # Required to create and apply labels
concurrency:
group: "terraform-drift-${{ inputs.env }}"
cancel-in-progress: false
jobs:
terraform-drift:
name: "Terraform Drift Detection"
runs-on: ubuntu-24.04
environment: "continuous-integration-${{ inputs.env }}"
timeout-minutes: 15
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
defaults:
run:
shell: "bash"
working-directory: "environments/${{ inputs.env }}"
steps:
- name: "Checkout"
- uses: "actions/checkout@v6"
+ uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6
- name: "Extract Variables"
id: "extract_vars"
env:
REGION_JSON_CONTENT: ${{ env.REGION_JSON }}
TARGET_ENV: ${{ inputs.env }}
run: |
REGION=$(echo "$REGION_JSON_CONTENT" | jq -r ".${TARGET_ENV}")
echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
- name: "Configure AWS Credentials"
- uses: "aws-actions/configure-aws-credentials@v6"
+ uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b" # v6
with:
role-to-assume: "${{ vars.ROLE_GITHUB }}"
role-session-name: "github-actions-${{ inputs.env }}"
aws-region: "${{ steps.extract_vars.outputs.REGION }}"
- name: "Set Terraform version"
id: "terraform_version"
run: |
echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
- name: "Setup Terraform"
- uses: "hashicorp/setup-terraform@v4"
+ uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e" # v4
with:
terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
- name: "Set up Python"
- uses: "actions/setup-python@v6"
+ uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" # v6
with:
python-version: "3.14"
- name: "Setup Python Environment"
run: |
make bootstrap-ci
- name: "Terraform Init"
run: |
terraform init -input=false
- name: "Check if any changes are planned"
id: "check_drift"
run: |
terraform plan -no-color -input=false -detailed-exitcode
- name: Cleanup working dir
run: git clean -df
- name: "Record Config Drift Log Entry"
if: "steps.check_drift.outputs.exitcode == 2"
run: |
git \
-c user.name="${{ github.actor }}" \
-c user.email="${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" \
commit -m "Reconcile configuration drift" --allow-empty
- name: "Check for open PR with label"
id: "check_label"
if: "steps.check_drift.outputs.exitcode == 2"
run: |
LABEL="config-drift"
REPO="${{ github.repository }}"
COUNT=$(gh pr list --repo "$REPO" --label "$LABEL" --state open --json number --jq 'length')
echo "Found $COUNT open PR(s) with label '$LABEL'"
if [[ $COUNT -gt 0 ]]; then
echo "label_exists=true" >> $GITHUB_OUTPUT
else
echo "label_exists=false" >> $GITHUB_OUTPUT
fi
- name: "Create Pull Request"
if: "steps.check_label.outputs.label_exists == 'false'"
- uses: "peter-evans/create-pull-request@v8"
+ uses: "peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1" # v8
with:
token: "${{ steps.app-token.outputs.token }}"
base: "main"
branch: "create-pull-request/config-drift"
title: "[config-drift] Reconcile Terraform configuration drift in ${{ inputs.env }}"
commit-message: "[config-drift] Configuration drift record for ${{ inputs.env }}"
team-reviewers: "devops-members"
labels: |
config-drift
EOT
id = "aws-service-infrahouse-app:.github/workflows/terraform-drift.yml:main"
# (9 unchanged attributes hidden)
}
# module.aws_service_infrahouse_app.github_repository_file.terraform_drift_wrapper[0] will be updated in-place
~ resource "github_repository_file" "terraform_drift_wrapper" {
~ content = <<-EOT
---
name: "[Meta] Terraform Drift Detection"
- on:
+ on: # yamllint disable-line rule:truthy
workflow_dispatch:
schedule:
# Runs daily at 10:XX AM UTC
- cron: "57 10 * * *"
jobs:
config-drift-check:
strategy:
matrix:
- env: ["production","sandbox"]
+ env:
+ - "production"
+ - "sandbox"
uses: "./.github/workflows/terraform-drift.yml"
with:
env: "${{ matrix.env }}"
- secrets: "inherit"
notify_on_failure:
name: "Notify #infra slack on failure"
needs: ["config-drift-check"]
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- name: Post to Slack
- uses: "slackapi/slack-github-action@v2.1.1"
+ uses: "slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a" # v2.1.1
with:
method: "chat.postMessage"
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: "${{ vars.INFRA_NOTIFICATION_CHANNEL }}"
text: "❌ Terraform Drift Detection failed for ${{ github.repository }}. Check the workflow run for more details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}."
EOT
id = "aws-service-infrahouse-app:.github/workflows/terraform-drift-wrapper.yml:main"
# (9 unchanged attributes hidden)
}
# module.aws_service_infrahouse_app.github_repository_file.vuln_scanner[0] will be updated in-place
~ resource "github_repository_file" "vuln_scanner" {
~ content = <<-EOT
# This file is managed by Terraform in github-control repository
# Do not edit this file, all changes will be overwritten
---
name: OSV-Scanner PR Scan
on: # yamllint disable-line rule:truthy
pull_request:
branches: [main]
merge_group:
branches: [main]
permissions:
contents: read
pull-requests: write
- env:
- GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-
jobs:
vulnerability-check:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- name: Set up Python
- uses: actions/setup-python@v6
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.14"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Install osv-scanner
run: |
curl -fsSL -o /usr/local/bin/osv-scanner "https://github.com/google/osv-scanner/releases/download/v2.2.4/osv-scanner_linux_amd64"
chmod +x /usr/local/bin/osv-scanner
osv-scanner --version
- name: Detect vulnerabilities
+ env:
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: |
if [ -n "${{ github.event.pull_request.number }}" ]; then
ih-github scan \
--repo ${{ github.repository }} \
--pull-request ${{ github.event.pull_request.number }}
else
ih-github scan
fi
sast-check:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- name: Set up Python
- uses: actions/setup-python@v6
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.14"
- name: Install dependencies
run: pip install -r requirements.txt
- name: SAST
+ env:
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: |
if [ -n "${{ github.event.pull_request.number }}" ]; then
ih-github run \
${{ github.repository }} \
${{ github.event.pull_request.number }} \
semgrep scan --error
else
semgrep scan --error
fi
EOT
id = "aws-service-infrahouse-app:.github/workflows/vuln-scanner-pr.yml:main"
# (9 unchanged attributes hidden)
}
Plan: 2 to add, 6 to change, 0 to destroy.
Warning: Argument is deprecated
with module.ih_8_repos.github_repository.repo,
on modules/local-repo/repos.tf line 4, in resource "github_repository" "repo":
4: has_downloads = false
This attribute is no longer in use, but it hasn't been removed yet. It will
be removed in a future version. See
https://github.com/orgs/community/discussions/102145#discussioncomment-8351756
(and 6 more similar warnings elsewhere)
Warning: Deprecated attribute
on .terraform/modules/actions-runner-pem-493370826424-uw1/data_sources.tf line 11, in data "external" "secret_value":
11: "python", "${path.module}/assets/get_secret.py", data.aws_region.current.name, aws_secretsmanager_secret.secret.id, data.aws_iam_role.caller_role.arn
The attribute "name" is deprecated. Refer to the provider documentation for
details.
(and 5 more similar warnings elsewhere)
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: tf.plan
To perform exactly these actions, run the following command to apply:
terraform apply "tf.plan"
Releasing state lock. This may take a few moments...
metadata
eyJzMzovL2luZnJhaG91c2UtZ2l0aHViLWNvbnRyb2wtc3RhdGUvdGVycmFmb3JtLnRmc3RhdGUiOiB7InN1Y2Nlc3MiOiB0cnVlLCAiYWRkIjogMiwgImNoYW5nZSI6IDYsICJkZXN0cm95IjogMH19
…commit message The full CI plan surfaced two pre-existing drifts unrelated to the workflow hardening; since CD applies the whole plan on merge, fix them here so they don't ride along. - teams.tf set only `username` in the members block, so Terraform defaulted every team member to role "member" and wanted to demote infrahouse8 (the out-of-band admin) from maintainer. Add an explicit `team_role` per member (infrahouse8 = maintainer, others = member) and set it in the content block. - service-repo makefile_fragment had commit_message "Add makefiles/Makefile" while state held "Update makefiles/Makefile", producing metadata-only churn. Match the config to state. Only aws-service-infrahouse-app uses the module. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
infrahouse8
approved these changes
Jul 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The
vuln-scanner-pr.ymlsast-checkjob (Semgrep) that this module injects into every service repo reports 26 blocking findings against the workflow templates this same module ships — surfaced in aws-service-infrahouse-app#25. Since those files are Terraform-managed, the fix belongs here inmodules/service-repo/templates/and propagates to every service repo on the next apply.What
Semgrep findings (all fixed):
github-actions-mutable-action-tag— every third-party action pinned to a full 40-char commit SHA with a# vNcomment.trufflesecurity/trufflehog@main(a mutable branch ref) pinned tov3.95.8. Same-repo reusable-workflow calls (./.github/workflows/...) left unpinned.gha-workflow-env-secret(vuln-scanner-pr.yml) —GITHUB_TOKENmoved from workflow-levelenv:to the two steps that use it.secrets-inherit(terraform-drift-wrapper.yml) — droppedsecrets: inherit. The called workflow only uses the automaticGITHUB_TOKEN, always available to reusable workflows and not listable in asecrets:map (reservedGITHUB_prefix).Keeping pins current:
renovate.json—service-repopreviously injected none. Useshelpers:pinGitHubActionDigests(bumps SHA + comment on new releases), dashboard-approval gated, no automerge.Incidental lint fix:
jsonencode(...)flow arrays, so rendered output passes yamllint'scommasrule. Verified equivalent viaterraform console. Added the missingtruthydisable on the wrapper'son:key.Docs:
.github/README.mdinto each service repo (repo-README.md) explaining the managed workflows +renovate.json— the natural home for this sincerenovate.json(strict JSON) can't carry a managed-by banner. Placed under.github/so it doesn't shadow a hand-written root README.templates/README.mdresource map.Notes
.tftplrenders were verified locally withterraform console; the real plan runs in CI.README.md, GitHub will surface.github/README.mdas its homepage.🤖 Generated with Claude Code