Test yourself
Advanced scripting for DevSecOps
Final exam · 60 questions · answers explained as you pick
Advanced Bash engineering
12 questions
01Why does a counter incremented inside `cmd | while read ...` come out as 0?
Incorrect — read does not clear your counter; the scope does.
Correct — redirect with `while ...; done < <(cmd)` or enable `shopt -s lastpipe` to run that stage in the current shell.
Incorrect — $((...)) works fine; the value is just lost with the subshell.
Incorrect — export sends values to children, not back up to the parent.
02The difference between ( ... ) and { ...; } in Bash is that…
Correct — assignments and cd inside ( ) vanish on exit; inside { } they persist.
Incorrect — they differ precisely in whether a subshell is forked.
Incorrect — it is the other way around.
Incorrect — that is (( )); single parens group commands in a subshell.
03In redirection, why does `cmd 2>&1 >file` NOT send stderr to the file?
Incorrect — it is valid; the issue is ordering.
Incorrect — it can; order is what bites here.
Correct — redirections apply left to right and capture the current target — put >file first: `cmd >file 2>&1`.
Incorrect — FDs are freely reassigned by redirection.
04Process substitution `<(cmd)` evaluates to…
Incorrect — that is $(cmd), command substitution.
Correct — diff <(a) <(b) compares two streams with no temp files and no cleanup.
Incorrect — status is in $?, not process substitution.
Incorrect — that is $! after `cmd &`.
05`shopt -s lastpipe` changes behaviour so that…
Correct — it fixes the classic `cmd | while read` counter loss (with job control off).
Incorrect — it changes scoping, not speed.
Incorrect — only the last stage, and only with monitor mode off.
Incorrect — that is pipefail, a different option.
06Under `set -e`, why might `local out=$(false)` NOT abort the script?
Incorrect — the real reason is subtler than "never works".
Incorrect — false returns non-zero; the status is just masked.
Incorrect — it does not disable it globally.
Correct — declare/assign on one line hides the RHS status; split them: `local out; out=$(false) || handle`.
07What does `set -o pipefail` do?
Incorrect — that is closer to errexit, and not per-pipe.
Incorrect — no — it changes how a pipeline’s status is computed.
Correct — without it, `false | true` succeeds and a failed producer goes unnoticed.
Incorrect — there is no retry behaviour.
08The `-E` (errtrace) flag matters because it…
Incorrect — that is shopt -s extglob.
Correct — without -E your error trap silently does nothing in the very functions where errors happen.
Incorrect — that is -u nounset.
Incorrect — that is -x xtrace.
09Which parameter expansion gives the basename of `/var/log/app.log` with no fork?
Correct — strip the longest leading match up to the last slash -> app.log, no external `basename` process.
Incorrect — that strips the trailing /file, giving the directory (dirname).
Incorrect — that removes all slashes, not just the leading path.
Incorrect — that is a fixed-length substring, unrelated to the basename.
10Replacing `grep X | grep -v Y | awk '{print $3}' | sort | uniq -c` with one awk program is better mainly because…
Incorrect — awk is interpreted; that is not the win.
Incorrect — brevity is a side effect, not the point.
Correct — fewer processes and one pass mean faster and fewer failure points, especially in loops.
Incorrect — awk should not parse JSON; use jq.
11Why should you never parse JSON or YAML with grep/sed/awk?
Incorrect — speed is not the issue; correctness is.
Correct — use jq for JSON and yq for YAML — they parse the real grammar and return the exact field.
Incorrect — they read files fine; they just cannot model structure.
Incorrect — JSON is text; the problem is structure, not encoding.
12To print a real call stack from a Bash ERR trap you use…
Incorrect — those are unrelated runtime values.
Incorrect — xtrace is noisy tracing, not a structured stack.
Incorrect — PATH is not a call stack.
Correct — looping `caller $i` prints lineno/function/source for each frame — a map of how execution reached the failure.
12 questions · explanations appear as you answer
Bash for secure, resilient automation
12 questions
01Which signal can a script NOT trap?
Incorrect — SIGTERM is trappable — that is how you shut down gracefully.
Incorrect — SIGINT (Ctrl-C) is trappable.
Correct — SIGKILL (and SIGSTOP) cannot be caught, blocked or ignored — that is their purpose.
Incorrect — SIGHUP is trappable and often used for reloads.
02Putting teardown in a single function on the EXIT trap is preferred because it…
Correct — signal traps then just set intent and exit; the EXIT trap does the actual rm/unlock.
Incorrect — EXIT runs on all exits, not just success.
Incorrect — nothing catches SIGKILL.
Incorrect — it runs during exit; it does not block it.
03What does `timeout -k 5 30 cmd` do?
Incorrect — the numbers are a signal grace, not additive waits.
Correct — the -k grace guarantees a stuck process that ignores TERM is still killed, so nothing hangs forever.
Incorrect — timeout does not retry.
Incorrect — timeout does not allocate CPUs.
04When a wrapper script is just a launcher, why `exec` the final command?
Incorrect — speed is not the reason.
Incorrect — exec does the opposite — it replaces the shell.
Correct — a shell that forwards signals imperfectly is the usual cause of pods that take the full grace period to die.
Incorrect — exec replaces, it does not fork.
05A value like `--checkpoint-action=exec=sh` passed as a filename is an example of…
Incorrect — this is about a value being read as an option, not split.
Correct — end option parsing with `--` and/or use `-e`/`--` guards so a leading dash cannot become a switch.
Incorrect — no wildcard is involved.
Incorrect — unrelated to heredocs.
06Running `eval "cp $src $dst"` with attacker-influenced $src is dangerous because…
Correct — never build commands from data with eval; just run `cp -- "$src" "$dst"` with quoted args.
Incorrect — cp is fine; eval is the vulnerability.
Incorrect — performance is not the concern.
Incorrect — quoting is exactly what is missing, but eval is the core danger.
07Setting an absolute PATH at the top of a privileged script prevents…
Incorrect — that is an IFS/quoting concern.
Incorrect — performance is not the point.
Incorrect — that is set -u.
Correct — controlling PATH (and calling critical tools by full path) ensures you execute the real binary, not a hijack.
08Why is `mktemp` required instead of `/tmp/tool.$$`?
Incorrect — it is about safety, not brevity.
Correct — an attacker can pre-create the predictable path pointing at a file you then write as root.
Incorrect — mktemp does not compress.
Incorrect — mktemp location depends on TMPDIR, not RAM by default.
09Writing to a temp file and then `mv` into place gives you…
Incorrect — the benefit is atomicity, not speed.
Incorrect — rename does not compress.
Correct — rename is atomic within a filesystem, so consumers see either the old or the new file, never a partial one.
Incorrect — mv -f overwrites; it does not keep a backup unless you make one.
10ShellCheck warning SC2086 ("Double quote to prevent globbing and word splitting") is important because…
Correct — treat ShellCheck findings as errors in CI so an unquoted `rm -rf $VAR` cannot merge.
Incorrect — performance is irrelevant here.
Incorrect — it affects every unquoted expansion, not just echo.
Incorrect — it is a genuine security/correctness class, not cosmetic.
11In a bats test, the `run` helper is used to…
Incorrect — run wraps one command, capturing its result for assertions.
Correct — you then assert e.g. [ "$status" -eq 0 ] and [ "$output" = "1.4.2" ].
Incorrect — that is the skip helper.
Incorrect — run does not mock; it captures results.
12The `if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@"; fi` idiom exists so that…
Incorrect — it prevents double execution, it does not cause it.
Incorrect — it has nothing to do with privilege.
Correct — sourcing loads the functions for testing without triggering the entry point.
Incorrect — unrelated to error handling.
12 questions · explanations appear as you answer
Advanced Python engineering
12 questions
01For a CPU-bound task (hashing thousands of files), the GIL means you should use…
Incorrect — threads share one GIL and cannot run Python bytecode in parallel.
Correct — processes each have their own GIL, so CPU work actually runs on multiple cores.
Incorrect — asyncio is for concurrency on I/O waits, not CPU parallelism.
Incorrect — that leaves cores idle for CPU-bound work.
02For I/O-bound work (hundreds of HTTP calls), the appropriate tools are…
Correct — many requests are in flight at once; you are waiting on the network, not the CPU.
Incorrect — processes work but add pickling/overhead you do not need for I/O waits.
Incorrect — that serializes the waits and is slowest.
Incorrect — concurrency is exactly what helps overlapping I/O waits.
03Why is "Python has a GIL, so I do not need locks" wrong?
Incorrect — it does, in CPython.
Incorrect — threads need synchronization too.
Correct — share nothing and use a queue, or protect shared state with a lock.
Incorrect — threads DO share memory — that is why races happen.
04A single blocking call inside an asyncio program is dangerous because it…
Incorrect — it does not raise; it silently stalls.
Correct — use async-native libraries or push blocking work to an executor; asyncio is cooperative.
Incorrect — asyncio does not auto-thread blocking calls.
Incorrect — it is the opposite — it destroys concurrency.
05An asyncio.Semaphore in a fan-out is used to…
Correct — bounded concurrency protects the target service and your own file-descriptor limits.
Incorrect — it limits concurrency; it does not accelerate anything.
Incorrect — a semaphore does not retry.
Incorrect — it coordinates within the loop, not replaces it.
06A top-level `except Exception: log(...); ` that then exits normally is harmful because…
Incorrect — verbosity is not the problem.
Incorrect — performance is irrelevant here.
Incorrect — logging is fine; swallowing the failure is not.
Correct — let unexpected exceptions propagate, or catch specifically and exit non-zero.
07A frozen dataclass is a good fit for configuration because it…
Incorrect — speed is not the reason to choose it.
Correct — frozen=True plus type hints makes config safe to pass around and mypy-checkable.
Incorrect — it does not; you populate it explicitly.
Incorrect — dataclasses do not encrypt.
08The recommended precedence when resolving a configuration value is…
Correct — the most specific, most intentional source wins; fall back predictably.
Incorrect — that inverts it — a hard-coded default would beat an explicit flag.
Incorrect — ordering must be by specificity, not alphabetics.
Incorrect — an explicit flag should override the environment.
09When re-raising a caught error as a higher-level one, `raise DomainError(...) from err` is preferred because it…
Incorrect — it preserves, not suppresses, the chain.
Incorrect — raising does not retry.
Correct — the "during handling of the above" chain shows exactly what the underlying failure was.
Incorrect — it raises an exception, not a warning.
10You should retry an operation only when it is…
Incorrect — retrying a 400 or auth error just fails identically forever.
Correct — non-idempotent retries can duplicate side effects; permanent errors will never succeed.
Incorrect — a 404 is permanent; retrying wastes time.
Incorrect — bad input fails the same way every time.
11Exponential backoff WITH jitter is used on retries to…
Correct — random jitter spreads the retries so the recovering service is not hit by a synchronized wave.
Incorrect — backoff slows successive attempts, deliberately.
Incorrect — it improves odds and protects the dependency; it cannot guarantee success.
Incorrect — you still bound each attempt and the total.
12A context manager (with / ExitStack) guarantees that…
Incorrect — it can still raise; cleanup runs anyway.
Incorrect — acquisition happens on enter, not lazily.
Incorrect — exceptions propagate after cleanup unless you suppress explicitly.
Correct — files, locks, sessions and temp dirs are released even if the block raises — ExitStack composes several safely.
12 questions · explanations appear as you answer
Python for security automation
12 questions
01The safe way to run an external command with untrusted arguments is…
Incorrect — shell=True re-parses the string; metacharacters in arg execute.
Correct — an argument list is never re-parsed by a shell, so injected characters stay inert.
Incorrect — os.system runs through the shell — same injection risk.
Incorrect — eval executes arbitrary code; never.
02Every subprocess and HTTP call should set a timeout because…
Correct — the default for many calls is no timeout at all; a bounded wait fails loudly instead of hanging silently.
Incorrect — they bound the wait; they do not speed the call.
Incorrect — it is optional syntactically — which is exactly the trap.
Incorrect — the issue is hangs, not memory.
03With Paramiko, the correct production host-key policy is…
Incorrect — that silently defeats SSH’s MITM protection; lab-only.
Incorrect — you must set one; the default may reject or error unpredictably.
Correct — host-key verification is the control that stops man-in-the-middle; never auto-add in production.
Incorrect — a warning that still connects does not protect you.
04After `exec_command` over SSH, you must…
Incorrect — silent success on a failed remote command is a real bug.
Correct — the channel’s exit status is the remote command’s real result; branch on it.
Incorrect — unnecessary; just read the status.
Incorrect — stderr often holds the reason for a non-zero status.
05On an HTTP 429 response, the resilient behaviour is to…
Correct — 429 means slow down; retrying immediately makes rate limiting worse.
Incorrect — that amplifies the problem and may get you blocked.
Incorrect — 429 is transient — back off and retry, do not give up outright.
Incorrect — unrelated and unsafe.
06Processing a 50 GB log with generators (yielding one line at a time) instead of read().splitlines() gives…
Incorrect — the win is memory, not raw CPU.
Correct — a lazy pipeline processes huge inputs in a few MB instead of OOMing.
Incorrect — generators are lazy, not parallel.
Incorrect — streaming does not sort.
07A regex like `^(\w+\s*)+$` on untrusted input risks…
Incorrect — it is valid; the danger is runtime.
Incorrect — the danger is time, not missed matches.
Correct — nested quantifiers are a DoS vector; prefer specific, anchored classes and cap input length or use a timeout.
Incorrect — ReDoS is about time complexity, not encoding.
08Best practice for a tool’s logs is to…
Correct — machines can index JSON fields; separating streams keeps real output pipeable.
Incorrect — unstructured stdout logs are hard to index and pollute real output.
Incorrect — never log secrets — logs get shipped and stored.
Incorrect — stderr lets the platform collect logs; a local file alone is invisible to the pipeline.
09Loading configuration from untrusted YAML should use…
Incorrect — the full loader can construct arbitrary Python objects — code execution.
Correct — safe_load removes the object-construction (RCE) capability entirely.
Incorrect — never eval external data.
Incorrect — pickle on untrusted data is also RCE, and YAML is not pickle.
10pickle.loads(network_bytes) on data you did not produce is dangerous because…
Incorrect — the risk is code execution, not speed.
Incorrect — the risk is far worse than precision.
Correct — treat pickle as RCE on untrusted input; use JSON, which has no execution semantics.
Incorrect — no network is needed to exploit it.
11To generate an API token you must use `secrets.token_urlsafe()` rather than the `random` module because…
Correct — security-sensitive values (tokens, nonces, salts) require cryptographic randomness.
Incorrect — random is fine for simulations — just not for secrets.
Incorrect — the difference is unpredictability, not speed.
Incorrect — it can; it is just predictable.
12Comparing a provided secret to the expected one with `secrets.compare_digest` instead of `==` prevents…
Incorrect — that is not the purpose.
Incorrect — not the concern here.
Incorrect — unrelated.
Correct — early-exit equality leaks how many leading characters matched; compare_digest does not.
12 questions · explanations appear as you answer
Production hardening & delivery
12 questions
01Installing dependencies with `pip install --require-hashes -r requirements.txt` guarantees…
Incorrect — the opposite — it pins exact, verified versions.
Correct — hash pinning makes installs reproducible and defends the supply chain.
Incorrect — verification is about integrity, not speed.
Incorrect — it still installs them — just verified.
02`pip-compile --generate-hashes` produces…
Correct — you commit the lock and install from it, so two builds a week apart are identical.
Incorrect — it resolves and pins the whole transitive tree.
Incorrect — it compiles a lockfile; it does not create a venv.
Incorrect — that is a build step, not pip-compile.
03Tools like shiv and pex build…
Incorrect — they still need a compatible Python; they are not static binaries.
Incorrect — that is a different packaging path.
Correct — distribution becomes copying one .pyz/.pex file that just runs.
Incorrect — not what shiv/pex produce.
04A good tool container is built multi-stage on a distroless/slim base and runs as non-root because…
Correct — compile in a fat builder, copy just the artifact into a tiny final image, drop privileges.
Incorrect — the goal is smaller, and small is part of safer.
Incorrect — Python runs fine as non-root.
Incorrect — distroless deliberately includes fewer — that is the point.
05Bootstrapping a tool with `curl https://... | bash` is a supply-chain risk because…
Incorrect — curl over HTTPS is fine; piping unreviewed code to bash is the issue.
Correct — vendor or verify bootstrap scripts and pin what they install.
Incorrect — it can; that is what makes it dangerous.
Incorrect — speed is not the concern.
06Distinct, documented exit codes matter because…
Incorrect — they are an interface, not a performance feature.
Incorrect — they complement logs; they do not replace them.
Correct — a tool that only ever returns 0 or 1 forces callers to scrape stderr to know what happened.
Incorrect — exit codes do nothing cryptographic.
07A `--dry-run` mode plus idempotency together give you…
Correct — dry-run prints what would happen and changes nothing; idempotent actions check state before acting, so a re-run converges.
Incorrect — that is not the benefit.
Incorrect — idempotency is not rollback; it is converge-if-needed.
Incorrect — unrelated.
08Writing a Prometheus textfile with a `last_success_timestamp` gauge lets you…
Incorrect — metrics track health, not the tool’s data output.
Correct — trends and freshness of success/failure are what make unattended jobs observable.
Incorrect — metrics and logs are complementary.
Incorrect — that is cosign, not a metric.
09An append-only audit record for a state-changing tool should capture…
Incorrect — who and what are needed too.
Incorrect — output alone does not establish accountability.
Incorrect — an incident review and an auditor both ask for a who/what/when trail specifically.
Correct — that is the record an incident review and a compliance auditor ask for first.
10The primary value of generating an SBOM for your tool is…
Correct — a Bill of Materials turns a days-long scramble into a lookup.
Incorrect — an SBOM is an inventory, not a runtime feature.
Incorrect — inventory and testing are unrelated concerns.
Incorrect — signing is cosign; the SBOM is the inventory (which you can also sign as an attestation).
11Running `pip-audit` and `trivy image --exit-code 1 --severity HIGH,CRITICAL` in CI…
Incorrect — that is a formatter, not a scanner.
Correct — a dependency clean at release can turn vulnerable later, so scan on every build and on a schedule.
Incorrect — signing is a separate cosign step.
Incorrect — that is pip-compile.
12Keyless signing with cosign (tied to the CI job’s OIDC identity) lets a consumer…
Incorrect — signing is about provenance, not encryption.
Incorrect — it verifies origin; it does not accelerate pulls.
Correct — an admission controller or the consumer verifies the signature/identity before running the tool.
Incorrect — signing proves origin; you still scan for vulnerabilities.
12 questions · explanations appear as you answer