Step 334. Consolidating Your Portfolio — The Proof-of-Skill Package: Scattered Evidence into One Story

Step 334. Consolidating Your Portfolio — The Proof-of-Skill Package: Scattered Evidence into One Story

Level 4 — Professional | Difficulty ★★★☆☆ | Estimated time: 2 days (half a day of asset inventory + 1 day of flagship selection and the consolidated page + half a day for the résumé)

Prerequisites: Step 294’s blog portfolio v1, Steps 321–323’s CVE reports and open-source release, Steps 332–333’s talk materials, and every competition record so far. This chapter builds nothing new — it’s a collecting project chapter.

  • What you need: a blog with Write-ups, a GitHub account, competition records (CTFtime included), a certification list, and a Python environment. The inventory script is a measured tool you run against your own folders; the consolidated page’s finished look is a screen example.
  • Caution: a portfolio is a public document — mask real names, affiliations, and personal contacts, and write only figures a third party can verify (Step 294’s rule).
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Cases included in the portfolio use only retired machines, finished competitions, and analysis cleared for publication.

Your 348 steps so far are full of evidence. Over 300 completed assignments, dozens of Write-ups, CVE analysis reports, open-source repositories, competition records, certifications, and talk materials. Yet this evidence is scattered across your blog, GitHub, CTFtime, and hard drives. No hiring manager, no researcher proposing collaboration, will gather it for you.

Today’s work is turning that scattered evidence into a one-page story. Attaching links and numbers to a single sentence — "this is the kind of problems I’ve solved." It’s called personal branding, but its substance is organization and verifiability. If Step 294 was tidying one axis called the blog, today is the integration of every channel.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Inventory scattered assets (posts, reports, repos, competitions, certifications, talks) with a script
  • Select 5–7 flagship works by depth rather than volume, and attach a 4-line summary to each
  • Complete a consolidated portfolio page (an About page or a GitHub profile README)
  • Tidy your GitHub profile — pinned repositories and a sustained commit record
  • Convert the same content into a one-page A4 résumé — centered on "what I did," not "what I know"

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3 (inventory script), Markdown (consolidated page, résumé), GitHub profile settings
Today’s command python step334_inventory.py <asset folder> — aggregate assets + generate the consolidated-page draft
Concepts needed Inventorying, flagship-selection criteria, the 4-line summary (problem/approach/result/lesson), verifiability
Today’s deliverable A consolidated portfolio page (flagship summaries included) + a one-page résumé + a tidied GitHub profile

2-1. The Inventory — Count Before You Gather

Portfolio work begins not with writing but with a stock count. People underestimate how much they’ve accumulated — things lived as daily assignments feel like "the obvious." So count with the file system, not with memory.

The inventory covers seven kinds — Write-ups, CVE/vulnerability analysis reports, open-source repositories, competition records (ranks included), certifications, talk materials, and mentoring experience. Five of the seven can be machine-counted from files and public records, and today’s script handles that part.

2-2. Flagship Works — Depth, Not Volume

When the inventory is done, temptation arrives — the urge to list everything. That’s the portfolio’s first failure pattern. A reader’s time is finite, and 50 links have the same effect as 0 links — nothing gets clicked.

Flagship works: 5–7, by the four criteria inherited from Step 294.

Criterion Check question
Difficulty Was it a problem that pushed the me of that time
Narrative Do the trial-and-error and abandoned hypotheses show
Reproducibility Can a reader follow along with just commands and output
Field balance Not clustered in one field (though if a target role is set, lead with that field)

Level 4 adds one more criterion — widening the axis of diversity. Not 7 Write-ups, but a mix of Write-up + CVE report + open-source tool + competition record + talk materials — that’s what completes the picture not of "a solver" but of "someone who builds, analyzes, and teaches."

2-3. The 4-Line Summary — The Packaging Spec for Flagship Works

The summary format attached to each selected flagship is fixed at four lines.

Problem: what had to be solved (one sentence)
My approach: the first attempt and the point where it changed
Result: numbers — score, rank, duration, reproduced or not
Lesson: what stays with the next me (not a technique — a judgment criterion)

Why four lines: the reader. A hiring manager doesn’t click a flagship link and read the whole article — they read the summary beside the link first. Four lines fit inside that person’s 30 seconds, and the circuit of "problem–thinking–result–growth" must complete inside them. A summary with an empty "lesson" field isn’t finished — that field is the evidence that this person will be faster when they meet the same problem again.

2-4. Résumé Conversion — Same Content, Different Grammar

The consolidated page and the résumé have the same content and different grammar. The page serves a browsing reader; the résumé serves a scanning reader inside one A4 page.

The grammar difference converges on one thing — write skills as "what you did with them," not "what you know." "Python, network protocols, reversing capable" is a list of things known. "Discovered 2 vulnerabilities (CVE-XXXX-XXXX) with an automated fuzzing campaign," "5th place in a domestic major CTF finals (40 teams)" is a list of things done. Same skill — only the latter is verifiable, and only verifiable sentences connect to interviews.


3. Follow Along

3-1. Building the Asset Folder — Gathering the Evidence in One Place

Real assets are scattered across the blog, GitHub, and CTFtime, but for aggregation you build a local mirror folder. You’re only collecting links and filenames, so the copy burden is small.

portfolio/
├── posts/        ← Write-up Markdown (blog source as-is)
├── reports/      ← CVE / vulnerability analysis reports
├── repos/        ← open-source repos (folder names only — one empty folder per repo)
├── talks/        ← talk materials
└── records.csv   ← competition records: event,rank,teams,note

You build records.csv yourself — transcribing from the CTFtime team page and official scoreboards. This CSV becomes the single source for competition records. Set the rule that the résumé, the consolidated page, and the next competition application all use only this file’s numbers, and you prevent the accident of different documents disagreeing.

3-2. The Inventory Script — The Machine Counts First

A script that scans the mirror folder, aggregates assets, and even generates the consolidated page’s draft. Save it as step334_inventory.py.

# step334_inventory.py — portfolio asset inventory + consolidated-page skeleton generator
# usage: python step334_inventory.py <portfolio asset folder>
import csv
import re
import sys
from pathlib import Path

def count_md(folder: Path):
    if not folder.is_dir():
        return []
    return sorted(p.name for p in folder.glob("*.md"))

def read_category(path: Path):
    m = re.search(r"^category:s*(.+)$", path.read_text(encoding="utf-8"), re.M)
    return m.group(1).strip() if m else "(uncategorized)"

def main():
    root = Path(sys.argv[1])
    posts = count_md(root / "posts")
    reports = count_md(root / "reports")
    repos = sorted(p.name for p in (root / "repos").iterdir() if p.is_dir()) 
        if (root / "repos").is_dir() else []
    talks = count_md(root / "talks")

    cats = {}
    for name in posts:
        c = read_category(root / "posts" / name)
        cats[c] = cats.get(c, 0) + 1

    records = []
    rec = root / "records.csv"
    if rec.exists():
        with rec.open(encoding="utf-8") as f:
            records = list(csv.DictReader(f))

    print("=== Portfolio Asset Inventory ===")
    print(f"Write-ups: {len(posts)}  |  analysis reports: {len(reports)}"
          f"  |  open-source repos: {len(repos)}  |  talks: {len(talks)}")

    print("n[Write-up field distribution]")
    for c, n in sorted(cats.items(), key=lambda x: -x[1]):
        print(f"  {c}: {n}")

    print("n[Competition records]")
    for r in records:
        rank, total = r["rank"], r["teams"]
        pct = f"top {int(rank)/int(total)*100:.0f}%" 
            if rank.isdigit() and total.isdigit() else "no record"
        print(f"  {r['event']}: {rank} / {total} teams ({pct}) — {r['note']}")

    skel = root / "portfolio_draft.md"
    lines = [
        "# Consolidated Portfolio (draft — script-generated)",
        "",
        "One-line intro: (here — one sentence on what kind of problems you've solved)",
        "",
        "### Flagship works (pick 5–7 by hand — 4 lines each: problem/approach/result/lesson)",
        "1. (select from posts/) — ",
        "2. ",
        "",
        "### Quantitative record (script-filled section — keep only what's verifiable)",
        f"- {len(posts)} cumulative Write-ups (fields: "
        + ", ".join(f"{c} {n}" for c, n in sorted(cats.items(), key=lambda x: -x[1])) + ")",
        f"- {len(reports)} vulnerability analysis reports / {len(repos)} open-source repos / {len(talks)} talks",
        "- competitions: " + "; ".join(f"{r['event']} {r['rank']}/{r['teams']}" for r in records),
        "",
        "### Contact: (competition account — no real name or personal email)",
    ]
    skel.write_text("n".join(lines), encoding="utf-8")
    print(f"nConsolidated-page draft generated: {skel.name}"
          " — flagship selection and 4-line summaries are filled in by a human.")

if __name__ == "__main__":
    main()

Here’s the measured output from running it against a sample mirror folder (8 Write-ups, 2 reports, 3 repos, 1 talk, 3 competition records):

=== Portfolio Asset Inventory ===
Write-ups: 8  |  analysis reports: 2  |  open-source repos: 3  |  talks: 1

[Write-up field distribution]
  pwn: 7
  web: 1

[Competition records]
  QualifierCup: 12 / 180 teams (top 7%) — advanced from qualifiers
  FinalsMajor: 5 / 40 teams (top 12%) — finals 5th place
  IntlOnline: — / — teams (no record) — completed

Consolidated-page draft generated: portfolio_draft.md — flagship selection and 4-line summaries are filled in by a human.

How to read it: look at three things. ① Confirming the total — the anxiety of "I don’t have enough to show" gets corrected by numbers. The sample has only 8 posts, but your real folder will show dozens. The point is proven here: it’s not that you’re lacking — it’s that things were never organized. ② The field-distribution bias — the sample is pwn 7, web 1. This bias becomes the input to flagship selection (3-3). ③ The "no record" row — the IntlOnline event recorded completion only, no rank. Leaving an unverifiable figure blank is not a defect — it’s rule compliance.

The generated portfolio_draft.md‘s content is also measured, exactly as produced:

# Consolidated Portfolio (draft — script-generated)

One-line intro: (here — one sentence on what kind of problems you've solved)

### Flagship works (pick 5–7 by hand — 4 lines each: problem/approach/result/lesson)
1. (select from posts/) — 
2. 

### Quantitative record (script-filled section — keep only what's verifiable)
- 8 cumulative Write-ups (fields: pwn 7, web 1)
- 2 vulnerability analysis reports / 3 open-source repos / 1 talks
- competitions: QualifierCup 12/180; FinalsMajor 5/40; IntlOnline —/—

### Contact: (competition account — no real name or personal email)

Look at the boundary between machine-filled and blank — the script fills the quantitative record; the human fills the flagships and the one-line intro. This division of labor is exactly the principles of 2-1 through 2-3.

3-3. Selecting Flagships and Writing 4-Line Summaries

Pick 5–7 with 2-2’s criteria table. Here’s a selection screen example based on the sample folder:

■ Flagship selection results (screen example)

1. [Write-up] heap-house — the record of first solo-reproducing a
   House-of-heap-family technique
   Problem: attacking a binary with the latest glibc heap protections
   My approach: tried the house-of-X family in order; found the check
   logic on the 3rd
   Result: shell in 6 hours, 2 solves in the competition
   Lesson: enumerate the protection list first — that decides attempt order

2. [Report] CVE-2026-2222 independent analysis — backtracing the
   vulnerability from the patch diff
   Problem: independently pinpoint the vulnerability's cause from a
   published security patch
   My approach: set 3 changed functions in the diff as candidates and
   eliminated one by one with PoCs
   Result: root cause identified + reproduction PoC written, 12-page report
   Lesson: a patch is "a problem with the answer printed" — reading diffs
   is the starting point of research

3. [Open source] payload-fuzzer — a competition payload generator
   released as a public repo
   ... (4-line example omitted)

When selecting, look back at the field bias from 3-2’s output. If you’re clustered in pwn like the sample, the 1 web post becomes an automatic candidate — because flagships are simultaneously a list of "what I did best" and an advertisement of "the range I cover."

3-4. Placing the Consolidated Page — About or GitHub Profile README

The finished draft goes in one of two places. If your blog already has readers, the About page; if hiring views are the main purpose, the GitHub profile README (the special README placed in a repository named the same as your account). Both is fine, but manage content from one source — the portfolio_draft.md you just made is that source.

The GitHub profile tidying checklist has three items.

[ ] Select 6 pinned repositories — flagship tool + Write-up collection +
    CVE analysis repo (pin evidence, not trophies: only repos with
    well-written READMEs)
[ ] Check each pinned repo's README — first screen has 3 lines:
    "what it is / why / how to use"
[ ] Persistence of the commit graph — the goal is not daily commits
    but "never breaking"

One thing about the graph. What a hiring manager reads in it is not density but persistence — sparse green across a long stretch of time gives more trust than two months packed solid followed by a four-month void. Attempts to decorate the graph (auto-commit bots, etc.) backfire the moment a magnifying glass arrives.

3-5. The One-Page Résumé — Compressing the Same Evidence

Once the consolidated page is complete, compress it into one A4 page. The compression rule is 2-4’s single grammar — from "what I know" to "what I did."

■ Résumé skills-section conversion example (screen example)

[Before — things known]
Python, C, network protocol understanding, web vulnerabilities,
reversing, CTF experience

[After — things done]
- CTF: 5th place in a domestic major finals (40 teams), 50+ cumulative Write-ups
- Vulnerability analysis: 2 independent analyses of published patch diffs,
  reports with reproduction PoCs
- Open source: maintain 3 repos including a competition exploit library
  (READMEs and tests included)
- Talks/mentoring: 1 intro-to-heap seminar for beginners, N weeks of
  study-group mentoring

How to read it: every word from the "before" survives in the "after," but all moved into the object of a verb. "Reversing" is gone; "independent patch-diff analysis" remains. Remember that only the latter reads on a hiring screen. And the volume rule — the moment you exceed one page, the strongest sentence gets buried. Removing is the essence of résumé work.


4. Missions & Exercises

Mission — Consolidated Portfolio + One-Page Résumé

  1. Build the asset mirror folder in 3-1’s structure, and transcribe only verifiable competition records into records.csv.
  2. Run step334_inventory.py to confirm the asset totals and field distribution, and receive the generated portfolio_draft.md.
  3. Select 5–7 flagships with 2-2’s criteria table, attach a 4-line summary to each, and complete the draft — if a field bias shows, adjust the balance.
  4. Place the consolidated page (blog About or GitHub profile README) — final check of personal-data masking and figure verifiability.
  5. Tidy the 6 GitHub pinned repositories, and compress the same content into a one-page résumé in 2-4’s grammar.

Exercises

Exercise 1. Explain why portfolio work must begin as a "stock count" rather than "gathering," connecting it to the psychology by which people underestimate their own accumulation.

Exercise 2. Why is listing 7 Write-ups as flagships incomplete? Answer from the perspective of the selection criterion added at Level 4.

Exercise 3. If the "lesson" field of a 4-line summary is empty, what does that summary fail to prove?

Exercise 4. Explain why a "things known" list is weaker than a "things done" list on a résumé, from the perspectives of verifiability and connection to interviews.


5. Model Answers & Completion Criteria

Mission Model Answer

Check against these verification criteria.

  1. Completeness of the mirror folder: are all file-representable kinds among the seven asset kinds gathered — do file-less kinds like mentoring experience exist as notes in records.csv or a separate memo.
  2. Script execution: do the inventory output’s totals, distribution, and competition records match the actual assets.
  3. Flagship quality: do all 5–7 have 4-line summaries with no empty "lesson" fields — is axis diversity (posts/reports/tools/competitions/talks) secured.
  4. Placement completeness: is the consolidated page public, with every figure verifiable by a third party within one or two clicks.
  5. Résumé grammar: is the skills list fully converted into "things done" sentences, within one page.

Exercise Answers

Answer 1. Human memory compresses repeated experience into "the obvious" — a daily assignment feels not like accumulation but routine. So recollection feels smaller than reality, and that underestimation surfaces as the anxiety "I don’t have enough to show." The stock count is a procedure that corrects this distortion with the objective device of the file system. Script-counted numbers are facts, not feelings, and only on those facts do the judgments of selection and placement become accurate. Reverse the order — write first — and accidents follow: making anew what you thought you lacked, or omitting what you thought you had.

Answer 2. A Write-up-only list proves a single shape — "a problem solver." Level 4 flagships demand axis diversity — solving (Write-ups), analysis (CVE reports), building (open-source tools), competing (competition records), conveying (talks, mentoring). Hiring managers and collaboration proposers judge a candidate’s role potential from the combination of these five axes. A portfolio with only one axis, however deep, gives no answer to "can they do other things too?" Flagship selection is a skill audit and simultaneously a role design.

Answer 3. It fails to prove "is this someone who’ll do better next time." The three lines of problem–approach–result hold only past facts; only the lesson line shows the experience converted into a judgment criterion. From the reader’s view, a three-line summary says "they solved that problem"; a four-line summary says "that problem changed this person." Since hiring is both an audit of past results and an evaluation of future growth potential, the fourth line carries half the summary’s value.

Answer 4. Because "things known" are claims and "things done" are evidence. "Reversing capable" is a sentence the applicant can’t self-verify and the interviewer has no way to confirm from documents — it must be re-asked at interview, so it earns no points at the document stage. By contrast, "2 independent patch-diff analyses, reports with PoCs" verifies instantly via links, and changes the interview question from "can you?" to "how did you?" The latter interview is a seat where the applicant tells the story they know best; the former is a verification test bench. Same skill — the grammar decides the seat’s character.

Completion Criteria Checklist

  • [ ] I built the asset mirror folder (posts/reports/repos/talks/records.csv)
  • [ ] I ran step334_inventory.py and confirmed totals, field distribution, and competition records
  • [ ] I selected 5–7 flagships with the criteria table and completed 4-line summaries (especially "lesson")
  • [ ] I placed the consolidated page — checked figure verifiability and personal-data masking
  • [ ] I tidied the GitHub pinned repositories around evidence (3-line READMEs included)
  • [ ] I wrote the one-page résumé in "things done" grammar
  • [ ] I designated records.csv as the single source for competition records and unified every document’s numbers

6. Common Pitfalls & Fixes

Wall 1. I ran the script and got a folder-argument error

Symptom:

    root = Path(sys.argv[1])
                ~~~~~~~~^^^
IndexError: list index out of range

Cause: you didn’t pass the asset folder path as an argument (measured — running without the argument gives this same error).

Fix: run it with the folder attached, like python step334_inventory.py portfolio. Quote the path if it contains spaces. Missing subfolders (posts, etc.) don’t kill the script — it counts them as 0 — so you can start with only some in place.

Wall 2. My competition record shows "no record"

Symptom: a rank you wrote into records.csv gets treated as "(no record)."

Cause: the script computes the percentile only when both the rank and teams fields are pure numbers. Mixed strings like "12th" or "5/40 teams" aren’t recognized as numbers (measured — the sample’s IntlOnline row is this case).

Fix: put only numbers in the CSV’s rank and teams fields — 12, 40. Units like "place" and "teams" get attached by the script at output. For events where the rank is unknown (completion-only records), as in the example is the correct entry.

Wall 3. Picking flagships, I keep going past 10

Symptom: this is a flagship and so is that — can’t cut to 7.

Cause: you’re selecting by attachment, not criteria — the illusion that what took long effort is a flagship.

Fix: reverse the procedure — first draw an empty 7-cell table and fill only one cell per axis (solving/analysis/building/competing/conveying). When two candidates compete on the same axis, settle it by "can a reader grasp its value within 30 seconds" — the amount of struggle doesn’t transmit to readers. What drops out isn’t deleted; it remains in the full-list link on the consolidated page’s body. The flagships are the curtain; the list is the stage.

Wall 4. My numbers disagree between the consolidated page and the résumé

Symptom: the blog says "50 Write-ups," the résumé says "45," GitHub says "40-something."

Cause: each document was edited separately on different days — without a single source, documents always diverge.

Fix: return to 3-1’s rule — designate a single source for numbers (records.csv and the inventory script’s output), and every document copies from that output. When numbers change, fix the source first and regenerate the documents. The moment an interviewer lays two documents side by side, a number mismatch reads as "inflation" — even when it’s a mistake.

Wall 5. After tidying, mine looks thin compared to others — is it okay to publish as-is?

Symptom: seeing someone else’s dazzling portfolio brings self-loathing.

Cause: a comparison-target selection error — you’re comparing their final edition with your first consolidation. And much of the dazzle is decoration you haven’t tried yet.

Fix: correct it with the facts — it wasn’t that you were lacking; things were unorganized, and today you finished that organizing. The criterion is one: is every sentence verifiable. A verifiable 8 beats an unverifiable 80. A portfolio is not a finished product but a living document — the next competition, the next analysis makes this page’s next version. What today needs is v1’s publication, not completion.


7. Summary

Today’s Concepts

Concept One-line explanation
Inventorying A stock count by file system, not memory — correcting underestimation
Flagship selection Depth over volume + axis diversity (solve/analyze/build/compete/convey)
4-line summary Problem · approach · result · lesson — "lesson" is the growth evidence
Verifiability Only third-party-verifiable figures — the body of trust
Single source Numbers come from one records.csv — prevents cross-document divergence
Résumé grammar "Things done," not "things known" — evidence, not claims

Today’s Tools & Commands

Tool/command What it does
python step334_inventory.py <folder> Aggregates totals, field distribution, competition records + generates the page draft
Mirror-folder structure posts/reports/repos/talks + records.csv — where evidence gathers
GitHub profile README The special README in the repo named after your account — the first screen of hiring views
6 pinned repositories Pin evidence, not trophies — the 3-line README rule
One-page résumé compression The consolidated page compressed in "things done" grammar

The Core Instinct

A portfolio is not self-promotion — it’s an accumulation of verifiability. Today’s page is valuable not for its design but for its structure: pick any sentence at random and there’s a link and a number at its end. What that structure creates is trust, and trust is the only currency of hiring and collaboration.

And this page is not a finished product. The next competition’s rank, the next analysis’s report, the next repository updates it. What matters is that today you made it updatable — one source, a set structure, and knowledge of what fills the blanks. What remains is the next decision this page will be weaponized for: fixing your career direction.


Once every box is checked, Step 334 is complete.