Expadox Limited internship, cohort 3
Most teams ship code with known vulnerabilities because security checks happen too late, post-deployment, during a quarterly pen test, or never. ResponseOne's brief was to instrument a real application with SAST, DAST, dependency scanning, and secrets detection at the CI/CD level, then build a SOAR layer that ingests every finding, enriches it, and triggers the right response automatically, without a human copying data between tools.
Formal roles on the final report: Team Lead, Solution Architect, and Technical Lead. What that covered in practice:
Shuffle runs on RES-SVR-L001 as the automation core. Scanner findings from GitHub Actions POST to a webhook, and three conditional branches route each finding to the right playbook: a Secrets Alert straight to Slack, and a CVE or OWASP finding through NVD enrichment first for full CVE/CVSS context before alerting.
Two structural bugs surfaced while wiring the workflow, both fixed rather than worked around. The first: an early version of the canvas had an unintended connection between branches that would have caused cross-triggering. It was found during testing and corrected.
The second was more fundamental: Shuffle derives a workflow's startnode from the
actions[0]/graph structure rather than accepting a manually set
start/isStartNode value. With three parallel conditional branches
wired directly from the trigger, only one branch was ever structurally reachable, the CVE
and OWASP branches were silently skipped regardless of condition or payload, no error, just
no execution. The fix was to insert a single "Router" pass-through node between the trigger
and the three branches, funnelling every conditional path through it. Shuffle then correctly
assigned the Router as the startnode, resolving routing for all three branches.
With NVD enrichment wired in, a full test using CVE-2021-44228 (Log4Shell) confirmed the complete chain, enrichment lookup, CVSS/description detail, and a formatted Slack alert with real context rather than raw JSON.
Four scanner jobs, Secrets (Gitleaks), SAST (Semgrep), SCA (Trivy), and DAST (OWASP ZAP), run in GitHub Actions on every push, each gated to block the build on a High or Critical finding.
Verifying the gates surfaced a real logic bug rather than just confirming they ran. The
DAST gate's jq query used test("High"), a substring match against
ZAP's full riskdesc field (format: "<Risk> (<Confidence>)").
That incorrectly matched entries like "Medium (High)", where "High" described
ZAP's confidence, not its risk rating, a false-positive block on a Medium-risk finding. The
fix anchored the match with startswith("High") instead, verified against both
the live report and a synthetic test confirming the corrected query isolates only genuine
High-risk entries.
Each scan type was verified the same way: plant or surface a genuine finding, confirm the gate blocks the build, and confirm the alert reaches Shuffle and the notification channel with accurate data, not just that the job ran without error.
A synthetic AWS access key was planted in a test file to trigger detection. Gitleaks flagged it, the gate blocked the job (secrets are always treated as Critical), and Slack received a real-time alert with accurate repo, file, and line data.
A deliberate SQL injection pattern, an unsanitized string-concatenated query, was planted in a test file. Semgrep flagged it as the sole error-level finding, the gate blocked the job, and the alert reached Shuffle with the exact rule and file that triggered it.
Juice Shop ships as a prebuilt container with no manifest for Trivy to scan, so a synthetic
package.json declaring lodash 4.17.4, a version with a known Critical prototype
pollution CVE, was planted to give Trivy a real dependency to flag.
The baseline ZAP scan came back clean, leaving no genuine High-severity finding to verify against. Rather than fabricate one, the scan was switched from passive baseline mode to an active full scan against Juice Shop's known-vulnerable endpoints, surfacing two real High-severity findings: SQL Injection and Source Code Disclosure via File Inclusion.
Beyond the two Shuffle structural bugs and the riskdesc gate defect above, verification surfaced a steady stream of environment and logic issues, each diagnosed from logs and host-level diagnostics rather than assumption:
continue-on-error on the ZAP step and missing closing fi statements in nested bash conditionals caused job failures on three separate occasions across the DAST job aloneFixes were tested on isolated branches before merging into main, a practice adopted after an early direct-to-main merge introduced two untested regressions at once.
Shuffle's built-in dashboard tracked workflow executions directly, no separate Grafana/Prometheus stack needed. At the point captured below: 9 workflows, 100% success rate, 0 failed runs, and 257 total workflow executions.
The brief called for four scanners gating every commit, SOAR ingestion with no manual import, CVE/CVSS enrichment, three automated playbooks, a live dashboard, and a before/after MTTT comparison. What shipped matches the core of that closely: all four scanners gate real commits with live-tested logic bugs found and fixed rather than assumed away, ingestion runs webhook to webhook with zero manual steps, enrichment pulls real NVD data, and all three playbooks (Secrets, CVE, OWASP) are wired and verified with evidence across GitHub Actions, Shuffle, Slack, and email.
Due to tight submission deadline, a few brief items were not realized and hence are not evidenced in the final report: an explicit before/after MTTT figure with methodology, demonstrated deduplication logic across reruns, and a written threat model of the pipeline itself (what happens if a scanner is tampered with or bypassed).
The full project report, including every finding write-up, the complete challenges log, and additional screenshots, is documented on Notion.