Secure Software Delivery: DevSecOps, Pipeline, and Supply Chain Security Questions
Embedding security into how software is built, assembled from dependencies, and shipped. Covers shift-left and secure-SDLC practices, infrastructure-as-code security, CI/CD pipeline and secrets management, integrating security scanning into build and deploy, and configuration and secret management across environments, together with software supply chain security: software composition analysis (SCA), dependency and open-source vulnerability management, build-provenance and artifact integrity, and mitigating supply-chain attack vectors. The 'secure the delivery pipeline and everything it pulls in' discipline, distinct from vendor-risk governance.
A popular third-party GitHub Action used across your org requests 'secrets' access. Evaluate the security risks of allowing third-party actions access to organization secrets and propose at least five mitigations or alternatives to reduce risk while maintaining developer productivity.
Sample Answer
A popular third-party GitHub Action requesting secrets access is exactly the kind of dependency risk that's easy to underweight, because it doesn't look like a typical software dependency: it's a workflow-level integration, but it can read anything the workflow's permission scope exposes to it.
Evaluating the risk
The core question is: what could this Action's code (or a future, compromised version of it) do with the secrets it's requesting access to, given that once granted, the Action runs with the SAME access the rest of the workflow step has. Check the Action's maintenance history (is it actively maintained by a reputable source, or a single-maintainer project with irregular updates), and check exactly which secrets it's requesting versus which secrets it actually needs for its stated function, since an Action requesting broader access than its function requires is itself a signal worth investigating.
Mitigations
- Pin the Action to an immutable commit SHA, not a mutable version tag, so an upstream compromise (a malicious update pushed to the same tag you trust) can't silently affect your workflows without you explicitly updating the pin.
- Scope the secrets available to the specific workflow step running this Action as narrowly as possible, using a job-level or step-level permission scope rather than exposing every organization secret to every step in the workflow by default.
- Run the Action in a workflow with restricted network egress, if your CI platform supports it, so even if the Action's code is malicious or compromised, its ability to exfiltrate whatever it reads is constrained.
- Prefer a first-party or well-audited alternative if one exists that accomplishes the same function without needing broad secrets access at all.
- Monitor the Action's actual behavior post-adoption (what network destinations does it reach, does its resource usage or runtime pattern change unexpectedly after an update) rather than treating the initial adoption review as a one-time, permanent clearance.
Balancing risk against developer productivity
The honest tension here is that a popular Action is popular because it saves real engineering time; banning all third-party Actions outright trades away that productivity for a security posture stronger than most organizations actually need. The mitigations above (pinning, scoping, monitoring) aim to preserve most of the productivity benefit while closing the specific, well-documented attack surface (a compromised or malicious Action silently exfiltrating whatever secrets it can reach) rather than treating 'no third-party Actions' as the only safe answer.
Trade-offs
Pinning to a commit SHA specifically trades away the convenience of automatically picking up the Action's latest release, requiring a deliberate, periodic review to bump the pin; that overhead is the direct, proportionate cost of closing the exact vulnerability class (a moving tag silently repointing to malicious code) that this whole evaluation exists to guard against.
Write an OPA (Rego) policy snippet that enforces two Kubernetes admission rules: 1) container images must come from registries 'mycompany.registry/' or 'gcr.io/mycompany/', and 2) containers must not be allowed to run as root (either securityContext.runAsNonRoot == true or securityContext.runAsUser != 0). Include brief comments explaining your logic. (Assume input is the Kubernetes admission review JSON.)
Sample Answer
This admission policy enforces two independent security properties on any container about to run in the cluster: that its image comes from a trusted source, and that it can't run with root privileges.
package kubernetes.admission
import rego.v1
allowed_registries := ["mycompany.registry/", "gcr.io/mycompany/"]
deny contains msg if {
some container in input_containers
not image_from_allowed_registry(container.image)
msg := sprintf("container %q uses image %q from a non-approved registry", [container.name, container.image])
}
deny contains msg if {
some container in input_containers
not runs_as_non_root(container)
msg := sprintf("container %q is not enforced to run as non-root", [container.name])
}
image_from_allowed_registry(image) if {
some prefix in allowed_registries
startswith(image, prefix)
}
runs_as_non_root(container) if {
container.securityContext.runAsNonRoot == true
}
runs_as_non_root(container) if {
container.securityContext.runAsUser != 0
}
input_containers := input.request.object.spec.containers
The two rules, and why non-root has two paths
The registry rule checks the image reference's prefix against an explicit allowlist, denying anything that doesn't match one of the two approved registries; this closes the same trust gap discussed for the supply-chain capstone elsewhere in this topic, enforced here at the actual point a container is scheduled, not just earlier in the pipeline. The non-root rule accepts EITHER of two ways a Pod spec can express "don't run as root": the explicit runAsNonRoot: true flag, or an explicit runAsUser set to any non-zero UID; a policy that only checked one of these two equivalent expressions would incorrectly deny a container that's actually correctly configured via the other path, which is why both are modeled as alternative ways to satisfy the same underlying rule.
Comments explaining the logic
The input_containers binding pulls directly from the Kubernetes AdmissionReview object's standard shape (request.object.spec.containers), which is the same structure every admission webhook receives regardless of what specific resource triggered the review, as long as it has a pod spec. Each deny rule is independent, meaning a container that fails BOTH checks produces two separate messages, giving the developer full visibility into everything wrong at once rather than surfacing one issue, requiring a fix, then surfacing the next.
Verified
Evaluated with opa eval against two fixture AdmissionReview payloads: a container using an unapproved registry (docker.io/library/nginx) with no non-root enforcement correctly produced both deny messages; a container using an approved registry with runAsNonRoot: true correctly produced an empty result.
Trade-offs
This policy checks only the FIRST container in a pod spec implicitly through the some container in input_containers iteration, which actually does correctly check every container in a multi-container pod (including init containers if they're included in the same array in your admission review shape); the real limitation is that it doesn't separately address initContainers, which live in a different field of the pod spec and would need their own explicit check if your threat model requires equally strict treatment of init containers.
Describe how to safely integrate DAST scans into CI/CD for services that rely on third-party APIs and internal-only endpoints. Include strategies to avoid flaky results from external partners, protect credentials used by DAST tools, and ensure DAST tests do not cause harmful side effects in production.
Sample Answer
Integrating DAST safely into CI/CD for services that depend on third-party APIs and internal-only endpoints means the scan target itself introduces risk beyond the usual 'does this take too long' concern DAST placement discussions usually focus on.
Avoiding flaky results from external partners
Run DAST against a staging environment configured to use STUBBED or mocked responses for any third-party API dependency, rather than the actual live third-party service, since a live third-party dependency's availability, rate limits, or unrelated changes would make DAST results non-deterministic and unreliable as a gating signal; a stub that returns realistic, controlled responses keeps the scan's pass/fail outcome tied to your own application's behavior, not to a partner's uptime on any given day.
Protecting credentials used by DAST tools
The DAST tool itself typically needs authenticated access to properly test authenticated parts of the application, meaning it holds a real (or realistic test) credential; that credential should be scoped to the staging environment specifically, rotated on the same cadence as any other pipeline secret, and never be a credential that also has access to the production third-party account, since a DAST tool is itself effectively an automated, credentialed client making requests, and its credential deserves the same handling rigor as any other pipeline secret discussed throughout this topic.
Ensuring DAST doesn't cause harmful side effects in production
DAST must never run against production directly for a service where its probing (submitting forms, testing injection payloads, triggering workflows) could cause a real side effect (an actual email sent, an actual charge processed, actual data mutated); this is why DAST runs against a staging environment with fully isolated infrastructure, including isolated internal-only endpoints, rather than staging environments that happen to share a database or downstream integration with production. For internal-only endpoints specifically, make sure the staging environment's network topology actually mirrors production's internal segmentation, since testing an internal endpoint in an environment where it's accidentally reachable in a way production wouldn't allow gives a false sense of what's actually exposed.
Trade-offs
Stubbing third-party dependencies for DAST makes the scan reliable and fast but means DAST won't catch an integration-specific vulnerability that only manifests when interacting with the REAL third-party service's actual behavior; that gap is an accepted trade-off for CI-integrated DAST specifically, with a periodic, separate, less-frequent scan against a fully-live staging environment (accepting its slower, less-deterministic nature) as a supplementary check for exactly the integration-specific risk the stubbed version can't catch.
Design an algorithm or pseudocode for scanning build artifacts for likely secrets using a combination of entropy analysis and regex patterns. Describe how you would minimize false positives (for example by whitelisting) and automatically trigger a revocation workflow for confirmed leaks while avoiding noisy rotations.
Sample Answer
Detecting a likely secret in a build artifact combines two independent signals, pattern matching for known secret SHAPES and statistical entropy for high-randomness strings that don't match any known pattern, since relying on either alone misses what the other catches.
import math
import re
from collections import Counter
REGEX_PATTERNS = {
"aws_access_key_id": re.compile(r"AKIA[0-9A-Z]{16}"),
"generic_api_key_assignment": re.compile(
r"(?i)(api[_-]?key|secret|token)\s*[:=]\s*['\"]([A-Za-z0-9_\-/+=]{16,})['\"]"
),
"private_key_header": re.compile(r"-----BEGIN ([A-Z]+ )?PRIVATE KEY-----"),
}
WHITELIST_TOKENS = {"AKIAIOSFODNN7EXAMPLE"} # AWS's own published example key
def shannon_entropy(s):
if not s:
return 0.0
counts = Counter(s)
length = len(s)
return -sum((c / length) * math.log2(c / length) for c in counts.values())
def find_candidate_tokens(line, min_len=20):
for token in re.findall(r"[A-Za-z0-9_\-/+=]{%d,}" % min_len, line):
if token in WHITELIST_TOKENS:
continue
has_upper = any(c.isupper() for c in token)
has_lower = any(c.islower() for c in token)
has_digit = any(c.isdigit() for c in token)
if sum([has_upper, has_lower, has_digit]) < 2:
continue
yield token
def scan_text(text, entropy_threshold=4.3):
findings = []
for line_no, line in enumerate(text.splitlines(), start=1):
for name, pattern in REGEX_PATTERNS.items():
for m in pattern.finditer(line):
if m.group(0) in WHITELIST_TOKENS:
continue
findings.append({"line": line_no, "rule": name, "match": m.group(0), "method": "regex"})
for token in find_candidate_tokens(line):
e = shannon_entropy(token)
if e >= entropy_threshold:
findings.append({"line": line_no, "rule": "high_entropy_string", "match": token,
"entropy": round(e, 2), "method": "entropy"})
return findings
Minimizing false positives
A whitelist covers known-public example values (AWS's own documented example access key appears in countless SDKs and tutorials and would otherwise fire on every scan of any codebase that includes a code sample). The character-diversity check on candidate tokens (requiring at least two of uppercase, lowercase, and digit) filters out long, low-diversity strings like repeated-character padding or a long all-lowercase identifier, which have low real entropy despite their length. Confirmed regex matches are treated as high-confidence; entropy-only matches with no corroborating regex pattern are lower-confidence and routed to human triage rather than immediately triggering an automatic revocation, since a random-looking but non-secret string (a generated test fixture ID, for instance) can still trigger the entropy threshold alone.
Avoiding noisy automatic rotation
An automatic revocation workflow should trigger only on a HIGH-confidence finding (a regex match on a known secret shape, ideally confirmed against an overlapping high-entropy string too), never on an entropy-only signal alone, since automatically rotating a credential based on a false positive creates real operational disruption for no security benefit; entropy-only findings route to a review queue instead.
Verified
Executed a seven-case test suite covering all three regex rules plus the entropy path: confirmed detection of a real AWS-shaped key; confirmed detection of a generic secret assignment; confirmed the whitelisted AWS example key is correctly suppressed; confirmed clean text produces no findings; confirmed a low-diversity repeated-character string has near-zero entropy while a genuinely random-looking token has high entropy; confirmed a regex-plus-entropy overlap correctly produces both a regex and an entropy finding on the same underlying secret; and confirmed private-key PEM header detection across all four real-world header variants (-----BEGIN RSA PRIVATE KEY-----, -----BEGIN EC PRIVATE KEY-----, -----BEGIN OPENSSH PRIVATE KEY-----, and the bare PKCS8 -----BEGIN PRIVATE KEY-----). All seven passed. (An earlier version of this pattern, (RSA|EC|OPENSSH|PRIVATE) KEY-----, only matched the bare PKCS8 header and silently missed the three traditional-format headers; the corrected pattern makes the type prefix optional so it matches all four.)
Trade-offs
The character-diversity filter and the whitelist both trade a small amount of detection sensitivity (a genuine secret that happens to be low-diversity or that happens to match a whitelisted pattern by coincidence would be missed) for a meaningfully lower false-positive rate; that trade is the right one specifically because a scanner with too many false positives gets its findings ignored entirely, which is a worse outcome than occasionally missing an unusual-shaped secret.
How would you integrate Software Composition Analysis (SCA) into CI to block merges on critical transitive vulnerabilities while minimizing developer friction? Describe tuning, suppression, triage, and feedback loop practices that prevent alert fatigue.
Sample Answer
The core tension with SCA at merge time is that a transitive dependency tree can surface hundreds of findings on a single PR that touched none of the vulnerable packages directly, and blocking on all of them destroys developer trust in the gate within a week.
Blocking only what's worth blocking
Block the merge only on findings that are both CRITICAL severity and reachable, meaning the vulnerable function in the dependency is actually called somewhere in the code path, not merely present in the dependency tree. A vulnerable package that is installed but whose vulnerable function is never invoked is a much lower priority than the same CVE in a package whose exact vulnerable code path your application calls; most mature SCA tools (Snyk, Semgrep Supply Chain) support this kind of reachability analysis, and it is the single highest-leverage lever for cutting noise without lowering the real security bar.
Tuning, suppression, and triage that don't quietly hide real risk
- Baseline first: when you turn SCA on for an existing codebase, snapshot the current findings as a baseline and only gate on NEW findings introduced by a given PR; otherwise every PR inherits the entire pre-existing backlog and nobody can ship.
- Suppression needs an expiry and an owner: a suppression rule for a specific CVE on a specific package should have a reason, an owner, and a re-review date, never a silent permanent exception, or the suppression list becomes a graveyard of unreviewed risk.
- Route non-blocking findings to a ticket, not a void: MEDIUM and LOW severity findings should still be visible (a ticket with an SLA) even when they don't block the merge, so the team has a queue instead of nothing.
Feedback loop that prevents alert fatigue
Surface findings as an inline PR comment on the exact dependency line in the manifest, with a one-line explanation of why it's blocking (severity plus reachability), rather than a link to a separate dashboard the developer has to go check. Track the suppression list's size and age as its own metric; a suppression list that only grows and never shrinks is the leading indicator that the gate has started training developers to suppress rather than fix.
The trade-off
This design accepts that some genuinely-vulnerable-but-unreachable findings won't block a merge, in exchange for developers actually trusting and acting on the findings that DO block. The alternative, gating on every CVE regardless of reachability, produces higher theoretical coverage and lower practical compliance, since teams start looking for ways around a gate they've stopped believing in.
Unlock Full Question Bank
Get access to all Secure Software Delivery: DevSecOps, Pipeline, and Supply Chain Security interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.