In this lab you will
- Create the devsecops-lab repository with a minimal Flask service that later labs scan, package and deploy
- Pin application dependencies and keep developer tools in a separate requirements file
- Install the pre-commit framework with a few basic hooks
- Add a first workflow with a top-level read-only token, a timeout and a smoke test
In this lab you create the repository that every later lab adds to. By the end, you have a working service, pinned dependencies, a pre-commit setup ready for the secret scanner in module 2, and a first green pipeline.
Before you start
You need:
- a free GitHub account, with the GitHub CLI (
gh) installed and signed in (gh auth login) - Git and Python 3.11 or newer
- a Bash shell: Linux, macOS, or WSL with Ubuntu on Windows
Make the repository public. On a free account, branch rules for private repositories are limited, and public repositories run GitHub Actions on standard hosted runners at no cost. Nothing secret goes into this repository.
Step 1: create the repository and the service
-
Create the repository and clone it:
gh repo create devsecops-lab --public --clone cd devsecops-lab -
Create a virtual environment and install Flask:
python3 -m venv .venv source .venv/bin/activate pip install flask pip freeze > requirements.txt cat requirements.txtpip freezewrites the exact version of Flask and every library it pulls in. Do this before installing any developer tools, sorequirements.txtlists only what the application needs at runtime. -
Create
app.py:from flask import Flask, jsonify, request app = Flask(__name__) @app.get("/health") def health(): return jsonify(status="ok") @app.get("/hello") def hello(): # Cap the input length; never trust what the client sends. name = request.args.get("name", "world")[:50] return jsonify(message=f"Hello, {name}") if __name__ == "__main__": app.run(host="127.0.0.1", port=8000) -
Run it and check it answers:
python app.py & sleep 2 curl -s http://127.0.0.1:8000/health curl -s "http://127.0.0.1:8000/hello?name=lab" kill %1You should see
{"status":"ok"}and{"message":"Hello, lab"}.
Step 2: keep the wrong files out
-
Create
.gitignore:.venv/ __pycache__/ .env .env.* !.env.example *.pem *.keyIgnoring
.envmeans a local file of real credentials is never staged bygit add .. The!.env.exampleline lets you commit an example file with placeholder values so others know which variables exist. -
Check it works:
echo "DB_PASSWORD=not-a-real-password" > .env git status --short # .env must not appear rm .env
Step 3: set up the pre-commit framework
pre-commit runs checks before Git creates a commit. You add the secret scanner in module 2; for now, set up the framework with a few general hooks.
-
Install it and pin it in a separate developer requirements file:
pip install pre-commit pip show pre-commit | grep ^VersionCreate
requirements-dev.txtwith that version:pre-commit==X.Y.ZReplace
X.Y.Zwith the version you just saw. Developer tools live here, never inrequirements.txt, so they stay out of the application image you build in module 5. -
Create
.pre-commit-config.yaml:repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v0.0.0 # replaced by the next command hooks: - id: check-yaml - id: check-merge-conflict - id: check-added-large-files - id: end-of-file-fixer - id: trailing-whitespace -
Pin
revto the current release and install the hooks into this clone:pre-commit autoupdate pre-commit install pre-commit run --all-filesautoupdatereplacesv0.0.0with the latest tag. Check thatrevnow shows a real version. The firstrunmay fix whitespace in your files; that is expected.
Step 4: add the first workflow
-
Create
.github/workflows/pipeline.yml:name: pipeline on: push: branches: [main] pull_request: # Every job gets read-only access to the code unless it asks for more. permissions: contents: read jobs: smoke: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install runtime dependencies run: pip install -r requirements.txt - name: Start the service and check it answers run: | python app.py & for i in $(seq 1 20); do curl -fsS http://127.0.0.1:8000/health && exit 0 sleep 1 done echo "service did not become healthy" >&2 exit 1What each choice is for:
permissions: contents: readis the least privilege a checkout needs. Once apermissionsblock is present, everything it does not list is set to none.timeout-minutesstops a hung or abused job early rather than letting it run to the platform’s much longer default.- The job installs only
requirements.txt, proving the service runs with its runtime dependencies alone. @v4and@v5are tags their owners can move. You pin them to full commit SHAs in the module 3 lab.
-
Commit and push everything:
git add . git status --short # check: no .venv, no .env git commit -m "Add the Flask service, pre-commit and a smoke pipeline" git branch -M main git push -u origin main -
Watch the run:
gh run watchThe
smokejob should go green.
Step 5: check the token really is read-only
-
Open the latest run’s log and expand Set up job. Under GITHUB_TOKEN Permissions you should see
Contents: readand nothing else.Or from the terminal:
run_id=$(gh run list --limit 1 --json databaseId --jq '.[0].databaseId') gh run view "$run_id" --log | grep -A3 "GITHUB_TOKEN Permissions" -
Look at the repository’s default too: Settings → Actions → General → Workflow permissions. Set it to Read repository contents and packages permissions so a workflow that forgets its
permissionsblock still gets the safe default.
Check your work
-
devsecops-labis public and containsapp.py,requirements.txt,requirements-dev.txt,.gitignore,.pre-commit-config.yamland.github/workflows/pipeline.yml -
requirements.txtlists Flask and its dependencies only, with exact versions, and no pre-commit - A
.envfile is ignored by Git -
pre-commit run --all-filespasses - The
smokejob is green, with a 10-minute timeout and a read-only token - The repository’s default workflow permission is read-only
Keep the repository. The
module 2 lab
adds the secret scanner and protects main.
Key takeaways
- Pinned versions in requirements.txt mean the scanners in later modules check exactly what you run.
- Developer tools go in requirements-dev.txt so they never end up in the application image.
- A .gitignore that lists .env stops the most common accidental secret commit before it happens.
- Declare permissions: contents: read at the top of every workflow, and set a timeout on every job.
- A green smoke job is your baseline: every security job you add later sits beside it.
Lesson questions
Answer all 10, then check them. You need 7 right to pass this lab and unlock the next one. Try as often as you like; after three tries the answers and explanations are shown. These are our own questions, written for this course.
Courses are taught against the frameworks they name. That is alignment, not certification: no course is certified, approved or endorsed by NIST, the DoD, DISA, FedRAMP, the CMMC program or any other agency or standards body, and completing one does not make a system compliant.