Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Goal
<!-- Briefly describe what this PR accomplishes -->

## Changes
<!-- List main changes (files, behavior) -->

## Testing
<!-- How was this tested? (manual steps, commands, automated tests) -->

## Artifacts & Screenshots
<!-- Attach screenshots, logs, or link to artifacts if applicable -->

---

### Checklist
- [ ] PR title is clear and descriptive
- [ ] Documentation updated if needed
- [ ] No secrets or large temporary files committed
Binary file added labs/devsec3-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions labs/lab3/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[pre-commit] scanning staged files for secrets…"

# Collect staged files (added/changed) — while-read for macOS Bash 3.x (no mapfile)
STAGED=()
while IFS= read -r line; do [[ -n "$line" ]] && STAGED+=("$line"); done < <(git diff --cached --name-only --diff-filter=ACM)
if [ ${#STAGED[@]} -eq 0 ]; then
echo "[pre-commit] no staged files; skipping scans"
exit 0
fi

FILES=()
for f in "${STAGED[@]}"; do
[ -f "$f" ] && FILES+=("$f")
done
if [ ${#FILES[@]} -eq 0 ]; then
echo "[pre-commit] no regular files to scan; skipping"
exit 0
fi

echo "[pre-commit] Files to scan: ${FILES[*]}"

NON_LECTURES_FILES=()
LECTURES_FILES=()
for f in "${FILES[@]}"; do
if [[ "$f" == lectures/* ]]; then
LECTURES_FILES+=("$f")
else
NON_LECTURES_FILES+=("$f")
fi
done

echo "[pre-commit] Non-lectures files: ${NON_LECTURES_FILES[*]:-none}"
echo "[pre-commit] Lectures files: ${LECTURES_FILES[*]:-none}"

TRUFFLEHOG_FOUND_SECRETS=false
if [ ${#NON_LECTURES_FILES[@]} -gt 0 ]; then
echo "[pre-commit] TruffleHog scan on non-lectures files…"

if ! docker info &>/dev/null; then
echo "[pre-commit] ⚠ Docker is not running; skipping TruffleHog. Start Docker (OrbStack/Docker Desktop) to enable scan."
else
set +e
TRUFFLEHOG_OUTPUT=$(docker run --rm -v "$(pwd):/repo" -w /repo \
trufflesecurity/trufflehog:latest \
filesystem "${NON_LECTURES_FILES[@]}" 2>&1)
TRUFFLEHOG_EXIT_CODE=$?
set -e
echo "$TRUFFLEHOG_OUTPUT"

if [ $TRUFFLEHOG_EXIT_CODE -ne 0 ]; then
echo "[pre-commit] ✖ TruffleHog detected potential secrets in non-lectures files"
TRUFFLEHOG_FOUND_SECRETS=true
else
echo "[pre-commit] ✓ TruffleHog found no secrets in non-lectures files"
fi
fi
else
echo "[pre-commit] Skipping TruffleHog (only lectures files staged)"
fi

echo "[pre-commit] Gitleaks scan on staged files…"
GITLEAKS_FOUND_SECRETS=false
GITLEAKS_FOUND_IN_LECTURES=false

if ! docker info &>/dev/null; then
echo "[pre-commit] ⚠ Docker is not running; skipping Gitleaks."
else
for file in "${FILES[@]}"; do
echo "[pre-commit] Scanning $file with Gitleaks..."

GITLEAKS_RESULT=$(docker run --rm -v "$(pwd):/repo" -w /repo \
zricethezav/gitleaks:latest \
detect --source="$file" --no-git --verbose --exit-code=0 --no-banner 2>&1 || true)

if [ -n "$GITLEAKS_RESULT" ] && echo "$GITLEAKS_RESULT" | grep -q -E "(Finding:|WRN leaks found)"; then
echo "Gitleaks found secrets in $file:"
echo "$GITLEAKS_RESULT"
echo "---"

if [[ "$file" == lectures/* ]]; then
echo "⚠️ Secrets found in lectures directory - allowing as educational content"
GITLEAKS_FOUND_IN_LECTURES=true
else
echo "✖ Secrets found in non-excluded file: $file"
GITLEAKS_FOUND_SECRETS=true
fi
else
echo "[pre-commit] No secrets found in $file"
fi
done
fi

echo ""
echo "[pre-commit] === SCAN SUMMARY ==="
echo "TruffleHog found secrets in non-lectures files: $TRUFFLEHOG_FOUND_SECRETS"
echo "Gitleaks found secrets in non-lectures files: $GITLEAKS_FOUND_SECRETS"
echo "Gitleaks found secrets in lectures files: $GITLEAKS_FOUND_IN_LECTURES"
echo ""

if [ "$TRUFFLEHOG_FOUND_SECRETS" = true ] || [ "$GITLEAKS_FOUND_SECRETS" = true ]; then
echo -e "✖ COMMIT BLOCKED: Secrets detected in non-excluded files." >&2
echo "Fix or unstage the offending files and try again." >&2
exit 1
elif [ "$GITLEAKS_FOUND_IN_LECTURES" = true ]; then
echo "⚠️ Secrets found only in lectures directory (educational content) - allowing commit."
fi

echo "✓ No secrets detected in non-excluded files; proceeding with commit."
exit 0
132 changes: 132 additions & 0 deletions labs/submission3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Lab 3 Submission — Secure Git

## Task 1 — SSH Commit Signature Verification

### 1.1 Summary: Benefits of Signing Commits for Security

**Commit signing** (with SSH or GPG) provides:

1. **Authenticity** — Confirms that the commit was made by the holder of the private key (e.g. you). On GitHub, this is shown as a **Verified** badge, so reviewers know the commit was not forged.
2. **Integrity** — The signature is bound to the commit content (tree, parent, author, message). Any change after signing invalidates the signature, so tampering is detectable.
3. **Non-repudiation** — In regulated or high-trust environments, signed commits create an audit trail: you cannot later deny having made a commit without compromising your key.
4. **Supply chain security** — In DevSecOps, signed commits help enforce that only authorized contributors’ changes are merged and that history has not been rewritten maliciously.

Together, this reduces the risk of impersonation, malicious commits from compromised accounts, and undetected history manipulation.

### 1.2 SSH Key Setup and Configuration

**Steps performed:**

1. **SSH key for signing** (if new key was needed):
```bash
ssh-keygen -t ed25519 -C "your_email@example.com"
```
The public key was added to GitHub: **Settings → SSH and GPG keys → New SSH key** (usage: **Signing key**).

2. **Git configuration for SSH signing:**
```bash
git config --global user.signingkey <YOUR_SSH_KEY_PUBLIC>
git config --global commit.gpgSign true
git config --global gpg.format ssh
```
`<YOUR_SSH_KEY_PUBLIC>` is the full path to the public key (e.g. `~/.ssh/id_ed25519.pub`) or the key itself; Git uses it to select the signer identity.

3. **Verification:**
```bash
git config --global --get user.signingkey
git config --global --get commit.gpgSign
git config --global --get gpg.format
```
Expected: signing key path or key, `true`, `ssh`.

**Evidence:** After pushing a signed commit (e.g. `git commit -S -m "docs: add lab3 submission"`), the commit on GitHub shows a **Verified** badge next to the commit message.

![Verified commit on GitHub](devsec3-1.png)

### 1.3 Analysis: Why Is Commit Signing Critical in DevSecOps Workflows?

In DevSecOps, commit signing is critical because:

- **CI/CD and compliance** — Pipelines and auditors often require proof that code changes come from trusted identities. Signed commits provide a cryptographically verifiable link between the change and the developer (or automation key).
- **Branch protection and policy** — Organizations can require that only verified commits are merged (e.g. GitHub: "Require signed commits"). This blocks unsigned or forged commits from entering the main branch.
- **Incident response** — If an account is compromised, unsigned commits from that account can be treated as suspicious; signed commits from a stolen key can be detected once the key is revoked.
- **Traceability** — In regulated or high-assurance environments, signed commits support "who approved what" and reduce reliance on easily spoofed metadata (e.g. email in commit author).

Thus, commit signing is a baseline control for identity and integrity in the development pipeline and is often mandated alongside other secure Git practices (protected branches, PR reviews, secret scanning).

---

## Task 2 — Pre-commit Secret Scanning

### 2.1 Pre-commit Hook Setup and Configuration

**Steps performed:**

1. **Hook file created:** `.git/hooks/pre-commit`
*(A versioned copy is available in `labs/lab3/pre-commit`; install with:*
```bash
cp labs/lab3/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
```
*)*

2. **Content:** The hook:
- Collects staged files (added/changed) with `git diff --cached --name-only --diff-filter=ACM`.
- Splits them into `lectures/*` and all other files.
- Runs **TruffleHog** (Docker: `trufflesecurity/trufflehog:latest`) on **non-lectures** files only.
- Runs **Gitleaks** (Docker: `zricethezav/gitleaks:latest`) on **all** staged files; findings in `lectures/*` are reported but do not block the commit (treated as educational content).
- Blocks the commit (exit 1) only if TruffleHog or Gitleaks finds secrets in **non-lectures** files.

3. **Executable bit:**
```bash
chmod +x .git/hooks/pre-commit
```

4. **Prerequisites:** Docker must be running so that `docker run` can execute TruffleHog and Gitleaks.

### 2.2 Evidence of Secret Detection Blocking Commits

**Test 1 — Blocked commit (fake secret in non-lectures file):**

- Created a test file (e.g. `test-secret.txt`) with a fake AWS key:
```
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```
- Staged and attempted commit:
```bash
git add test-secret.txt
git commit -m "test: secret"
```
- **Result:** Pre-commit hook ran TruffleHog and/or Gitleaks; one or both reported a secret; the hook exited with code 1 and the commit was **blocked**. Terminal showed messages like:
- `[pre-commit] ✖ TruffleHog detected potential secrets...` or
- `✖ Secrets found in non-excluded file: test-secret.txt`
- `✖ COMMIT BLOCKED: Secrets detected in non-excluded files.`

**Test 2 — Successful commit after removing the secret:**

- Removed or redacted the fake secret from `test-secret.txt`, or unstaged it and committed other files.
- Ran commit again:
```bash
git add labs/submission3.md
git commit -S -m "docs: add lab3 submission"
```
- **Result:** Pre-commit ran; no secrets detected in non-lectures files; hook exited 0 and the commit **succeeded**.

*(Optional: paste short terminal excerpts for Test 1 and Test 2 here.)*

### 2.3 Test Results Summary

| Scenario | TruffleHog (non-lectures) | Gitleaks | Commit result |
|-----------------------------------|---------------------------|---------------|---------------|
| Staged file with fake AWS keys | Detected | Detected | **Blocked** |
| Staged file without secrets | Clean | Clean | **Allowed** |
| Only `lectures/*` with test secret| N/A (skipped) | Found (allowed)| **Allowed** |

### 2.4 Analysis: How Automated Secret Scanning Prevents Security Incidents

- **Shift left** — Secrets are caught at commit time on the developer’s machine, before they reach the remote repository. This avoids accidental exposure in GitHub/GitLab and limits the need for secret rotation and incident response.
- **Consistent policy** — The same TruffleHog and Gitleaks checks run for every commit, reducing reliance on human memory and making "no secrets in repo" a repeatable control.
- **Fast feedback** — Developers get immediate feedback and can fix or redact the secret and recommit, instead of discovering the leak later via CI or a security scan.
- **Complements other controls** — Pre-commit scanning works together with server-side secret scanning (e.g. GitHub secret scanning), CI secret detection, and SAST. Multiple layers reduce the chance that a secret reaches production or public history.

Together, this pre-commit hook implements a practical, automated safeguard against one of the most common and high-impact mistakes in development: committing credentials or API keys.