From b847ad707cb8fa09b91ff85ced49bfbb5d344be3 Mon Sep 17 00:00:00 2001 From: Paulo Freitas Date: Tue, 22 Sep 2026 19:28:54 -0300 Subject: [PATCH] feat(docs): bilingual onboarding guide, and serve the guide's own favicon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a language button beside the theme button. Both languages live in the same file: every section carries a
and a
, and a data-lang attribute on the root hides one of the two. Switching is instant, the choice is remembered, and a first-time visitor gets whichever language their browser asks for. One file rather than two was deliberate. The repository already keeps two README files in sync by hand and CONTRIBUTING has to ask contributors to remember; here the translations sit adjacent, so an edit to one has the other in view. The HTML ships data-lang="pt" so a reader without JavaScript still gets a single-language page rather than both at once, and the CSS only ever writes display:none rules, so a shown block keeps the display its own styles gave it — .facts is flex, and a display:block here would flatten it. The favicon link pointed at ../media/favicon.png. Since docs/ is the published root, that resolves above the site, into the organization's own Pages site, which happens to serve a byte-identical file at that path — so it loaded by coincidence and would break the day that site reorganized its assets. It also declared image/x-icon for a PNG. The icon now ships inside docs/ and is declared image/png. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 13 + docs/favicon.png | Bin 0 -> 7795 bytes docs/index.html | 1002 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 997 insertions(+), 18 deletions(-) create mode 100644 docs/favicon.png diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7b6ad..2c03450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to this project are documented here. The format follows ## [Unreleased] +### Added + +- The onboarding guide is bilingual. A language button beside the theme button + switches between Portuguese and English without reloading the page. Both + languages live in the same file, side by side, so they cannot drift apart the + way two separate files can; the choice is remembered per visitor, and a + first-time visitor gets whichever language their browser asks for. Without + JavaScript the page still renders, in Portuguese. + ### Fixed - The onboarding guide no longer serves `uses: Tooark/ci-security-scanner@v1.1.0` @@ -14,6 +23,10 @@ All notable changes to this project are documented here. The format follows The affected spans now carry Cloudflare's `email_off` opt-out. - The `[1.0.0]` and `[1.1.0]` links at the bottom of this file pointed at a `v1.0.0` tag that was never pushed, so both 404'd. +- The guide serves its own favicon. The link pointed at `../media/favicon.png`, + which resolves above the published root — `docs/` is the site root — and only + appeared to work because the organization's site happens to serve an + identical file at that path. It also declared `image/x-icon` for a PNG. ## [1.1.0] - 2026-09-22 diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc382d45fff5e11b840e6e8397a5afa50f8d140 GIT binary patch literal 7795 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 - + - + -ci-security-scanner por dentro +CI Security Scanner + + @@ -590,14 +618,23 @@

Onboarding · Tooark

-

ci-security-scanner
por dentro

-

+

ci-security-scanner
por dentro

+

ci-security-scanner
from the inside

+ +

Este repositório não contém um scanner de segurança. Ele contém a embalagem reutilizável de um — publicada duas vezes, uma para GitHub Actions e uma para GitLab CI, com os mesmos nomes, os mesmos defaults e a mesma regra de precedência nas duas. Este guia percorre cada arquivo e, principalmente, o porquê de cada decisão.

-
    +

    + This repository does not contain a security scanner. It contains the reusable + packaging for one — published twice, once for GitHub Actions and once for GitLab CI, with the + same names, the same defaults and the same precedence rule on both sides. + This guide walks every file and, above all, the reasoning behind each decision. +

    + +
    • Tooark/ci-security-scanner
    • v1.1.0
    • scanner 1.9
    • @@ -605,12 +642,25 @@

      ci-security-scanner
      por dentro

    • 2 plataformas
    • MIT
    - +
      +
    • Tooark/ci-security-scanner
    • +
    • v1.1.0
    • +
    • scanner 1.9
    • +
    • 7 scans
    • +
    • 2 platforms
    • +
    • MIT
    • +
    + +
    + + +
-
+ +
+

Repository map

+

+ Twenty-odd files, in five groups with clearly distinct purposes. +

+ +
+
Deliverable — GitHub
+
action.ymlThe public API signature: ~45 inputs, 3 outputs, 4 steps
+
src/run-scanner.shThe implementation: turns inputs into a docker run
+ +
Deliverable — GitLab
+
templates/full-scan.ymlAll three scanners in one job, one consolidated report
+
templates/image-scan.ymlOne template per command — same structure
+
templates/filesystem-scan.yml
+
templates/config-scan.yml
+
templates/repo-scan.yml
+
templates/dockerfile-lint.yml
+
templates/secret-scan.yml
+ +
Integrity
+
VERSIONSingle source of truth for versions
+
scripts/check-sync.shContract test between the two front ends
+
scripts/validate-templates.pyStructural validation of the GitLab templates
+ +
The project's own CI
+
.github/workflows/ci.ymlLint, validation and a self-scan on every PR
+
.github/workflows/release.ymlPublishes the release and moves the floating tags
+
.github/dependabot.ymlWeekly updates of the referenced Actions
+ +
Documentation and hygiene
+
examples/Copy-ready pipelines for both platforms
+
README.md · README.pt-BR.mdDocumentation in English and Portuguese
+
CHANGELOG.md · LICENSEVersion history and the MIT licence
+
.editorconfig · .gitattributesForce UTF-8 and LF line endings
+
.gitignoreIgnores scan-reports/ and .cache/
+
+ +
+ Windows +

+ .gitattributes forcing LF is not fussiness: a .sh saved + with CRLF simply does not execute on a Linux runner, and the error you get + (bad interpreter) does not say so. +

+
+
05 +

A fachada GitHub: action.yml

Uma composite Action é uma Action feita de outras Actions e comandos shell, sem código compilado. @@ -971,11 +1300,88 @@

O detalhe que vale internalizar

e é uma classe de bug real e frequente em GitHub Actions.

+
+ +
+

The GitHub front end: action.yml

+

+ A composite Action is an Action made of other Actions and shell commands, with no compiled code. +

+ +

+ When someone writes uses: Tooark/ci-security-scanner@v1.1.0, GitHub downloads this + repository and runs the steps declared here inside the caller's job. The file has four + blocks. +

+ +

inputs: — the parameters

+

+ About 45 of them, each with a description and a default. Think of them as + the library's public function signature: it is the contract with consumers, and changing it breaks + people. +

+ +
trivy-severity:
+  description: "Severities included in the report, e.g. CRITICAL,HIGH."
+  required: false
+  default: ""
+ +

+ Almost all of them are default: "". That is not laziness — it is the + precedence rule, explained under Decisions. +

+ +

outputs: — the return value

+

+ exit-code, reports-dir and report. They let the next step read + the result with steps.scan.outputs.exit-code. +

+ +

branding:

+

Icon and colour on the GitHub Marketplace. Purely cosmetic.

+ +

runs: — the function body

+

Four steps, in this order:

+ +
    +
  1. cache — works out in bash whether the Trivy cache should be used and under which + key. dockerfile-lint and secret-scan skip it, because Hadolint and + Betterleaks never read the CVE database.
  2. +
  3. Restore the Trivy database — actions/cache/restore@v6.
  4. +
  5. scan — calls bash "$GITHUB_ACTION_PATH/src/run-scanner.sh". + This is where the work happens. Everything else is input and output.
  6. +
  7. Save the Trivy database and Upload security reports — save the + cache and publish the reports as an artifact.
  8. +
+ +

The detail worth internalising

+

In the env: block of the scan step:

+ +
env:
+  ARK_IN_TRIVY_SEVERITY: ${{ inputs.trivy-severity }}
+  ARK_IN_IMAGE:          ${{ inputs.image }}
+ +

+ Inputs are not interpolated into the text of the script. They arrive as environment + variables prefixed with ARK_IN_. +

+ +
+ Why +

+ If an input were pasted straight into the script body — run: docker run ... ${{ inputs.image }} + — a value like "; rm -rf / # would become a command. Passing through env:, + it is always just a value. It is the same reasoning as prepared statements against SQL + injection, and it is a real, frequent class of bug in GitHub Actions. +

+
+
06 +

A fachada GitLab: templates/

Um CI/CD Component do GitLab é um arquivo YAML com duas metades separadas por ---. @@ -1046,11 +1452,86 @@

E o catálogo?

templates/ quando a versão muda, empurra a tag e publica no catálogo interno. O GitHub continua sendo a fonte da verdade; nada é escrito no espelho.

+
+ +
+

The GitLab front end: templates/

+

+ A GitLab CI/CD Component is a YAML file with two halves separated by ---. +

+ +
# top half: parameter declaration
+spec:
+  inputs:
+    trivy_severity:
+      default: ""
+      regex: "^[A-Z,]*$"      # GitLab validates before anything runs
+      description: "..."
+---
+# bottom half: the template of the generated job
+"$[[ inputs.job_name ]]":
+  stage: $[[ inputs.stage ]]
+  image: ...
+  script: ...
+ +

+ The $[[ inputs.x ]] syntax is substituted by GitLab at the moment the pipeline is + assembled — before anything runs. +

+ +

+ GitLab's spec: is more expressive than GitHub's inputs:: it accepts + options: (an enum), regex: and type:. The project uses that — + trivy_exit_code only accepts "", "0" or "1". + Anyone passing something else gets an error before the pipeline runs, not halfway through it. +

+ +

How it is consumed

+ +
# in the .gitlab-ci.yml of the project to be scanned
+include:
+  - remote: "https://raw.githubusercontent.com/Tooark/ci-security-scanner/v1.1.0/templates/full-scan.yml"
+    inputs:
+      stage: security
+      image: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
+      trivy_severity: "CRITICAL,HIGH"
+ +

+ A job named security:full-scan then appears in the pipeline. Since it is an ordinary + job, anything the inputs do not cover can be overridden by redeclaring its name: +

+ +
"security:full-scan":
+  needs: ["build"]
+  cache: []                # disable the Trivy cache
+  variables:
+    TRIVY_SCANNERS: "vuln,secret,misconfig,license"
+ +

examples/gitlab/remote-include.gitlab-ci.yml — the design's escape hatch.

+ +

Why 7 nearly identical files

+

+ Because on GitLab one template equals one generated job. There is no file that generates “whichever + job you pick”. The duplication is imposed by the platform — and that is exactly why + check-sync.sh exists: duplication without automated verification rots. +

+ +

What about the catalog?

+

+ GitLab's CI/CD Catalog only lists components hosted on the instance itself, so a GitHub + repository cannot be published to it directly. The + examples/gitlab-catalog-mirror/ folder carries the pipeline for a mirror + project that closes the gap: it polls GitHub releases on a weekly schedule, copies + templates/ across when the version moves, pushes the tag and publishes to + the internal catalog. GitHub stays the source of truth; nothing is authored in the mirror. +

+
07 +

O motor: src/run-scanner.sh

246 linhas de bash que traduzem ~40 variáveis ARK_IN_* em um docker run. @@ -1154,11 +1635,114 @@

O que o script faz, em ordem

Devolve a posse dos arquivos. A imagem roda como usuário não-root (uid 1000), que não é o usuário do runner. Sem o chown de volta no fim, o passo de upload de artifact não conseguiria ler os relatórios que o scanner acabou de escrever.

+
+ +
+

The engine: src/run-scanner.sh

+

+ 246 lines of bash that translate ~40 ARK_IN_* variables into a docker run. + Almost every comment in it documents a bug someone already hit. +

+ +

The full path of one run

+ +
+ + + + + + + + + 1 + the team's workflow + uses: ...@v1.1.0 + with: command, image + + + + 2 + action.yml + inputs → ARK_IN_* + restores the cache + + + + 3 + run-scanner.sh + builds the docker run + only what is non-empty + + + + 4 + ark-tools + Trivy · Hadolint + Betterleaks + + + + 5 + output + scan-reports/ + exit code → the gate + + + the runner host + inside the container · uid 1000 · workspace mounted at /workspace + +
+

On GitLab steps 2 and 3 do not exist: the job is the container, and the template does the same work in before_script.

+ +

What the script does, in order

+ +

Validates the command against a fixed list of seven. No input ever becomes an + arbitrary command.

+ +

Sets up the volumes — the workspace goes to /workspace, the reports to + /reports, the Trivy cache to /home/app/.cache/trivy.

+ +

Rewrites paths. Someone writing path: src/ means the runner's disk, but + the container sees /workspace/src. The to_container_path function does that + translation and lets URLs and absolute paths through untouched.

+ +

Forwards only what is non-empty. This is the five-line function that holds up the + precedence rule of the whole project:

+ +
add_env() {
+  local name="$1" value="$2"
+  [ -n "$value" ] || return 0     # ← the heart of the precedence rule
+  DOCKER_ARGS+=(-e "$name=$value")
+}
+ +

Secrets come from the environment, never from inputs. TRIVY_TOKEN, + REPORT_TOKEN, registry credentials and friends are read from the job environment and + passed to the container; the log prints ***.

+ +
+ Trap +

+ Input values appear in the rendered pipeline configuration, visible to anyone with read access to + the project. A token passed as an input is a leaked token. +

+
+ +

Builds the arguments per command. Each of the seven has a different shape. One + example of care for usability: in full-scan, if you passed no image at all, the script + turns on FULL_SCAN_SKIP_IMAGE=true itself rather than letting Trivy fail trying to scan an + empty string.

+ +

Hands file ownership back. The image runs as a non-root user (uid 1000), which is + not the runner user. Without the chown at the end, the artifact upload step could not read + the reports the scanner had just written.

+
08 +

Versão única e testes de contrato

A parte que mais ensina, e o padrão mais transferível deste repositório para qualquer outro projeto. @@ -1231,11 +1815,90 @@

A regra prática

python3 scripts/validate-templates.py ./scripts/check-sync.sh shellcheck -s bash src/run-scanner.sh scripts/check-sync.sh +
+ +
+

Single source of truth and contract tests

+

+ The part with the most to teach, and the most transferable pattern in this repository. +

+ +

The problem

+

+ The same scanner version appears in ten places: seven templates, + action.yml and two fallbacks inside run-scanner.sh. Updating from + 1.9 to 2.0 and forgetting one template means a project silently running the + old version. YAML has no compiler; nobody notices. +

+ +

The solution

+

VERSION is the single constant:

+ +
# Single source of truth for versions in this repository.
+COMPONENT_VERSION=1.1.0
+SCANNER_IMAGE=ghcr.io/tooark/security-scanner
+SCANNER_VERSION=1.9
+ +

And scripts/check-sync.sh is the contract test that fails CI when anything diverges. It + checks four invariants:

+ +
    +
  1. Every template and action.yml pins exactly the image declared in + VERSION.
  2. +
  3. In each template, every declared ARK_IN_* is actually read, and every + ARK_IN_* read was declared.
  4. +
  5. Every ARK_IN_* set in action.yml is forwarded by + run-scanner.sh.
  6. +
  7. Every copy-paste reference in the docs and the examples pins + COMPONENT_VERSION.
  8. +
+ +
+ Why +

+ Items 2 and 3 catch the nastiest bug in this kind of project: you add an input, document it, and + forget to connect one of the ends. The input exists in the documentation, the user sets the value, + and nothing happens. Silently. The script turns that into a red build. +

+
+ +

+ scripts/validate-templates.py does the equivalent for the structure of the GitLab + templates: the --- separator is present, every $[[ inputs.x ]] references a + declared input, every declared input is used (a dead input is a lie in the documentation), + default is among the options, and the template defines exactly one job. +

+ +

+ The comment at the top of the file gives the best possible justification: GitLab only reports these + errors when a pipeline is created — which is to say, in a consumer's project, not in + this one. Far too late. +

+ +

The practical rule

+
+ When adding an input +

+ Touch all four places, or check-sync.sh will tell you: + the template's spec:inputs, the template's variables: block as + ARK_IN_*, action.yml, and src/run-scanner.sh. +

+
+ +

To run the validations locally, before opening the PR:

+ +
python3 -m pip install pyyaml
+
+python3 scripts/validate-templates.py
+./scripts/check-sync.sh
+shellcheck -s bash src/run-scanner.sh scripts/check-sync.sh
+
09 +

O CI do próprio projeto

Uma dobra que confunde no começo: a pasta .github/workflows/ não tem nada a @@ -1306,11 +1969,86 @@

dependabot.yml

digest: sem isso, pinar congelaria a dependência em vez de controlá-la. Pinar sem um mecanismo de atualização não é segurança, é dívida.

+
+ +
+

The project's own CI

+

+ A fold that confuses at first: the .github/workflows/ folder has nothing to + do with what the project delivers. It is this repository's CI, like any other project has. +

+ +

ci.yml — runs on push and pull request

+ +

+ Job lint: parses every YAML file in the repository, runs the two + validation scripts, runs shellcheck (a bash linter) and actionlint (a GitHub + Actions linter). +

+ +

+ Job self-scan: the project scans itself with its own Action + (uses: ./). That is dogfooding, and it works as an integration test — if the Action is + broken, this repository's own CI breaks. +

+ +

Two lines are worth noticing:

+ +
permissions:
+  contents: read     # the job token only reads — least privilege
+ +
uses: docker://rhysd/actionlint@sha256:b1934ee5...   # pinned by digest
+ +
+ Why +

+ A Docker tag (:1.7.12) can be repointed by the image owner at any moment. A SHA-256 + digest is immutable. Since that third-party container runs inside this project's CI, pinning by + digest stops unreviewed code from starting to run here without a single commit. +

+
+ +

release.yml — runs when a v*.*.* tag is pushed

+ +

+ Revalidates everything, checks that the tag agrees with COMPONENT_VERSION + — tag v1.0.1 with VERSION=1.0.0 is a refused release — creates the GitHub + Release with generated notes, and moves the floating tags. +

+ +
+ + + + + + + + + + +
ReferenceResolves toUse when
v1.1.0Exactly that releaseReproducible pipelines
v1.1Newest patch of the 1.1 lineAutomatic patch updates
v1Newest release of the 1.x lineAutomatic minor and patch updates
mainUnreleased workNever in a pipeline you care about
+
+ +

+ It is the Actions ecosystem convention — actions/checkout@v7 works this way. The README + is honest about the cost: pinning v1 means running code you have not reviewed after the + next release. +

+ +

dependabot.yml

+

+ Opens a weekly PR updating the referenced Actions. The comment in the file closes the digest + reasoning: without it, pinning would freeze the dependency instead of controlling it. + Pinning without an update mechanism is not security, it is debt. +

+
10 +

Decisões e armadilhas

As escolhas que parecem estranhas na primeira leitura, e o motivo de cada uma. Esta seção é a que mais @@ -1416,11 +2154,123 @@

Duas pegadinhas menores

GitLab não permite que um input omita uma palavra-chave, e lista vazia é como se diz “qualquer runner”. Pipelines aceitam; apenas o schema JSON do editor do GitLab reclama. +
+ +
+

Decisions and traps

+

+ The choices that look strange on a first read, and the reason behind each. This is the section that + saves the most time when something goes wrong. +

+ +

The precedence rule — why default: "" everywhere

+ +
input  >  CI variable / job env  >  image default
+ +

+ An empty input is never forwarded. That lets a project set + TRIVY_SEVERITY once as a global variable and leave the input blank in every job, instead + of repeating it. Set both and the input wins. +

+

+ If an empty input were forwarded, it would overwrite the global variable with an empty string and + break that pattern. It is the difference between undefined and "" — and the + project handles it explicitly on both platforms, with add_env() in bash and + ark_apply_inputs in the templates. +

+ +

The Trivy cache, and the separate save step

+

+ Trivy's CVE database is large. Downloading it on every build is the slowest part of a scan and the + easiest way to hit a registry rate limit. Both platforms cache it, with different strategies: GitLab + points TRIVY_CACHE_DIR at .cache/trivy inside the project and uses a fixed + key shared by every branch; GitHub uses actions/cache with one entry per day per scanner + version, falling back to the previous day — so Trivy refreshes an existing database instead + of fetching a whole one. +

+ +
+ The subtlety that explains the extra step +

+ On GitHub, the normal actions/cache saves in a post step, and post steps + are skipped when an earlier step failed. But this Action fails by design when it + finds a vulnerability. Without the separate save step, only repositories that find nothing would + ever populate the cache — precisely the ones that need it least. +

+
+ +

+ An operational detail for self-hosted GitLab instances: by default the runner cache lives on the + runner's own disk. With several runners, a job only hits the cache if it lands on the runner that + wrote it. Configuring distributed caching (S3 or equivalent) in config.toml is + what makes the hit rate consistent. +

+ +

entrypoint: [""] in every GitLab template

+

+ The image has an entrypoint that execs ark-tools directly. GitLab Runner keeps the image + entrypoint and attaches a shell to it. Without clearing it, the job dies before the script runs. +

+ +

GIT_DEPTH: "0" / fetch-depth: 0

+

+ By default CI does a shallow clone (only the last commit) because it is faster. But + Betterleaks looks for secrets in the history — and the classic case is exactly the + key that was committed and “removed” afterwards. +

+ +
+ Silent failure +

+ With a shallow clone, Betterleaks sees almost nothing and does not complain. The + job passes green and you believe you are protected. That is why every template and every example + forces full depth. +

+
+ +

docker-socket: "true" hands over root on the runner

+

+ Mounting /var/run/docker.sock gives the container full control of the host's Docker + daemon — it can start a privileged container and read the whole machine. It is off by default. It is + only needed to scan an image built in the same job; an image already pushed to a registry + does not need it. On a shared self-hosted runner, the recommendation is to push to the registry and + scan from there. +

+ +

Reports can contain the secrets they found

+

+ Two settings turn an artifact into a disclosure: betterleaks_redact: "0" writes detected + secrets in cleartext, and adding secret to trivy_scanners puts Trivy's + findings in the report. Artifacts are downloadable by anyone with read access to the repository. The + default is 100% redaction, and the log prints only rule, file, line and short commit — never the + secret. +

+ +

File ownership on the GitHub runner

+

+ The image runs as uid 1000, which is not the runner user. The script creates the reports directory + with broad permissions during the scan, hands ownership back at the end and tightens the permissions + again; the workspace is declared a git safe directory through environment variables. On an + ephemeral runner this is immaterial; on a self-hosted one with concurrent jobs there is a short + window in which another job could write there. +

+ +

Two smaller gotchas

+
    +
  • dockerfile-lint handles one file per job. For several, use + full-scan with dockerfiles: "a,b,c", or include the template once per + file with a different job_name.
  • +
  • The empty default of tags renders as tags: []. GitLab + has no way for an input to omit a keyword, and an empty list is how you say “any runner”. + Pipelines accept it; only GitLab's editor JSON schema objects.
  • +
+
11 +

Traduzindo para vocabulário de dev

O repositório inteiro mapeia quase um para um em conceitos que você já usa todo dia. @@ -1471,6 +2321,62 @@

Por onde começar a ler o código

Guia de onboarding · Tooark/ci-security-scanner v1.1.0 · scanner 1.9
A referência autoritativa de cada input é o bloco spec:inputs do template correspondente.

+
+ +
+

Translated to dev vocabulary

+

+ The whole repository maps almost one-to-one onto concepts you already use every day. +

+ +
+ + + + + + + + + + + + + + + + + +
In the repositoryDevelopment equivalent
action.yml · inputs: blockPublic signature / interface
src/run-scanner.shImplementation behind the interface
templates/The same interface ported to another platform
VERSIONSingle constant (DRY)
scripts/check-sync.shContract test between the implementations
scripts/validate-templates.pyLinter / static analysis
.github/workflows/ci.ymlThe test suite running on every PR
self-scan jobIntegration test (dogfooding)
examples/Executable documentation
Tags v1.1.0 / v1SemVer + line alias
CHANGELOG.mdRelease notes
+
+ +

Where to start reading the code

+

In this order, which is the order the data flows in:

+ +
    +
  1. examples/github/security-scan.yml — what the end user writes
  2. +
  3. action.yml, skipping straight to the runs: block at the end — what + happens when they write that
  4. +
  5. src/run-scanner.sh — how it becomes a docker run
  6. +
  7. templates/full-scan.yml — the same path, in the GitLab dialect
  8. +
  9. scripts/check-sync.sh — what stops the two from diverging
  10. +
+ +
+ If you only have ten minutes +

+ Read Decisions and traps and the four-places rule under + Single source of truth. That is what comes up most in code review and in + broken pipelines. +

+
+ +

+ Onboarding guide · Tooark/ci-security-scanner v1.1.0 · scanner 1.9
+ The authoritative reference for every input is the spec:inputs block of the matching + template. +

+
@@ -1511,8 +2417,63 @@

Por onde começar a ler o código