chapter 04
Troubleshooting
Every problem below really happened in this implementation β none of these are hypothetical. Click any symptom to see the cause and the fix.
Gitleaks
A test AWS key doesn't trigger any alert at all
cause
The string used was AKIAIOSFODNN7EXAMPLE β the official example access key used across thousands of AWS documentation pages. Precisely because of that, it's on Gitleaks' own internal allowlist: without that exception, any repository containing a snippet of AWS documentation would generate a constant false positive.
fix
Use any other string in the same format (AKIA + 16 characters), as long as it isn't that specific value. Nothing else in the config needs to change β Gitleaks' allowlist is about that exact value, not the whole pattern.
A test key typed into the terminal ends up in the file as bullets (β’β’β’β’β’β’β’β’β’β’β’β’)
cause
Something in the terminal environment masked text matching the AWS Access Key pattern (AKIA + 16 characters) at the moment it was typed or pasted β before it ever became real file content. A plain echo would never mask anything on its own; the interception happened at a layer above the shell.
fix
Build the string from two separate shell variables and concatenate them only when writing the file, instead of typing/pasting the full value at once:
p1="AKIA5G3J"
p2="XNQPZ7W2M4KD"
printf 'aws_secret_access_key = "%s%s"\n' "$p1" "$p2" > secret-test.txt
Since no single command contains the full pattern, the masking mechanism has nothing to intercept.
YAML / GitHub Actions
A new job added to the workflow simply doesn't show up
cause
Wrong indentation β the new job (sonarcloud:) was written at the same level as the previous job's properties (name:, runs-on:, steps:), instead of at the same level as the gitleaks: key itself. In YAML, a child key needs more indentation than its parent β with the wrong indentation, the parser reads everything as belonging to a single job, with duplicate keys (name, runs-on, steps each appearing twice).
fix
Validate the structure before committing, instead of trusting a visual read:
python3 -c "import yaml; d=yaml.safe_load(open('file.yml')); print(list(d['jobs'].keys()))"
If the list is missing an expected job, it's indentation. Rewriting the whole file via heredoc (instead of editing specific lines) avoids the error of manually counting spaces.
A cat > file << 'EOF' command ends up inside the file itself
cause
The block was pasted somewhere that didn't interpret the heredoc as a shell command β for example, inside an already-open text editor (nano/vim) instead of the bash prompt. The result is a file whose literal content starts with cat > file << 'EOF' and ends with EOF, instead of containing just the heredoc's body.
fix
Run the full command directly at the terminal prompt (not inside an editor that's already open), and validate the result before committing. For a config file like TOML, a real parser is more reliable than reading it visually:
python3 -c "import tomllib; tomllib.load(open('.gitleaks.toml', 'rb')); print('valid TOML')"
OWASP ZAP
A rule set to FAIL still shows up as WARN-NEW in the report β but the job fails anyway
apparent cause
The initial assumption was that the 10038 FAIL rule in zap-baseline-rules.conf was escalating that specific finding and bringing the job down because of it.
real cause
The action's own log reveals the actual reason: "By default ZAP Docker container will fail if it identifies any alerts during the scan!" β with fail_action: true, the job fails if any alert is found, at any severity, regardless of whether it's escalated to FAIL in the rules file. Rule 10038 stayed WARN-NEW in the report the entire time; the rules file, as written, had no effect on the outcome at all.
fix
No configuration change was needed β just the correct understanding: for this scenario, fail_action: true alone already guarantees the block. The rules file is still useful for deciding what to ignore (IGNORE), not for deciding what causes failure.
gh run view --log returns "logs will be available when it is complete"
cause
Not an error β the run is still executing. A ZAP scan takes noticeably longer than the Gitleaks/SonarCloud gates (1 to 3 minutes), so it's easy to ask for the log too soon.
fix
Wait for the run to finish before requesting the log:
gh run watch $RUN_ID
Pipeline design
The DAST workflow leaves the repository permanently "red" in Actions
cause
The workflow triggered on every push to master, but the target (Juice Shop) always has the same real findings β so every commit, even one completely unrelated to the pipeline, ended up failing. The gate was working correctly; the visual signal was just misleading, suggesting "broken pipeline" to anyone looking from the outside.
fix
Switch the trigger from push: branches: [master] to workflow_dispatch (manual trigger). More on why this better reflects how DAST works in practice is in chapter 05.