What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Security research

Step 323. Publishing an Open-Source Tool and Contributing to the Community — Code as a Work

Step 323Estimated practice · 3 hours

Level 4 — Reporting, CVE Analysis & Open-Source Contribution | Difficulty ★★★☆☆ | Estimated time: 3 hours

Prerequisites: the Git/GitHub basics from Steps 86–87 (init/commit/push/clone/bare repositories), and at least one tool you’ve built yourself in this book.

  • What you need: Git Bash, a tool of your own to publish (if you don’t have one, the portpeek port scanner you’ll build with us today). With a GitHub account you can publish for real; without one, practice the entire process identically using a local bare repository — this chapter’s measurements use the local simulation method.
  • Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Any tool you publish must carry an explicit usage scope: "for your own systems and authorized targets only."

Coming this far in the book, you’ve built scanners, automation scripts, and analysis tools. Tidying them up and uploading them to a public repository earns you three things — the pressure that raises code quality, community feedback, and proof of skill a recruiter can verify. Today you’ll pick one tool, publish it with a README and a license, and practice the full community cycle of receiving and merging someone else’s contribution (a fix branch). You’ll confirm with your own hands that the heart of good open source is documentation more than code.


1. Learning Objectives

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

  • Choose a tool to publish and clean its code into "a state others can use"
  • Write a README with six sections (intro / usage scope / installation / usage / options / license)
  • Choose and apply the MIT license, and explain the criteria for choosing a license
  • Create a public repository and push (GitHub or a local bare repository)
  • Perform the issue-response cycle of receiving, reviewing, and merging a contributor’s fix branch

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Git Bash + Python 3.10+ (the published tool uses the standard library only)
Today’s commands git init --bare, git remote add, git push -u, git checkout -b, git merge --no-ff
Concepts needed README, licenses (MIT), issues and PRs (pull requests), Step 87’s remote repositories
Today’s deliverable One public repository (README + license + usage scope) + one merged contribution

2-1. Why Publish — Three Kinds of Returns

A script living only on your computer is "good enough if it runs." The moment you publish, the standard changes — others read it, others run it, others ask questions. That pressure makes code better.

Career-wise, a single repository URL with a README and a commit history is a hundred times the evidence of the résumé line "proficient in Python." And incoming issues and fix proposals are free code review.

2-2. The README — Read Before the Code

Visitors don’t read the code first. They skim the README for five seconds and decide "is this worth using?" If the README doesn’t say "what it is, why, and how to use it," nobody uses it.

A security tool’s README needs six sections.

Section Contents
One-line intro What it is
⚠️ Usage scope Explicit statement: your own systems / authorized targets only
Installation What’s needed and how to get it
Usage Command examples and output examples
Options An arguments table
License Under what terms it may be used

The second section is unique to security tools. Without a usage-scope statement, a scanner or testing tool becomes an invitation to misuse the moment it’s distributed.

2-3. Licenses — The Answer to "May I Take This?"

Public code without a license file is legally "look but don’t touch." Only with a license can others use and modify it.

At the beginner stage the choice is simple. MIT — "use it freely, keep the copyright notice, and I’m not liable if something goes wrong" — is the most widely used, and it’s more than enough for this book’s tools. The GPL family is a copyleft license with a "modifications must also be published" condition; you can revisit that question when your purpose becomes clear.

2-4. Issues and PRs — The Grammar of the Community

An issue is a forum post saying "please fix this / how about this feature," and a PR (pull request) is a fix proposal saying "I fixed it this way — please accept it." Today you’ll reproduce this flow with local repositories — a contributor-role second clone fixes a bug and pushes a branch, and the original repository (maintainer role) reviews and merges it. The issue/PR screens on GitHub are just this same flow done with buttons on the web.


3. Follow Along

3-1. Choosing and Cleaning the Tool — portpeek

A good candidate for publication is "your own script you use most often." Today we’ll build portpeek, a cleaned-up version of this book’s port-scanner exercise. Here’s the code tidied for publication — no hardcoded paths, with argument handling (argparse) and error handling. Save it as portpeek.py.

#!/usr/bin/env python3
"""portpeek — single-host port scanner (educational tool for your own lab only)"""
import argparse
import socket


def scan(host, ports, timeout=0.5):
    open_ports = []
    for port in ports:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        try:
            if sock.connect_ex((host, port)) == 0:
                open_ports.append(port)
        except socket.gaierror:
            print(f"[error] host not found: {host}")
            return []
        finally:
            sock.close()
    return open_ports


def parse_ports(text):
    if "-" in text:
        a, b = text.split("-", 1)
        return range(int(a), int(b) + 1)
    return [int(x) for x in text.split(",")]


def main():
    p = argparse.ArgumentParser(description="port scanner for your own lab only")
    p.add_argument("host", help="target host (e.g., 127.0.0.1)")
    p.add_argument("--ports", default="1-1024", help="e.g., 22,80,443 or 1-1024")
    p.add_argument("--timeout", type=float, default=0.5, help="seconds (default 0.5)")
    args = p.parse_args()

    print(f"target: {args.host} / ports: {args.ports}")
    found = scan(args.host, parse_ports(args.ports), args.timeout)
    if found:
        for port in found:
            print(f"  [OPEN] {port}")
    else:
        print("  no open ports")


if __name__ == "__main__":
    main()

How to read it: the criteria for pre-publication cleanup are three — ① does running it require no paths from your own computer, ② does --help alone explain the usage, ③ does bad input get an answer in human language rather than a Python traceback?

3-2. Creating the Repository and the First Public Commit

Input:

mkdir portpeek && cd portpeek
git init
# after saving portpeek.py
printf "__pycache__/n*.logn.envn" > .gitignore
git add portpeek.py .gitignore
git commit -m "portpeek 0.1.0 — first public release"
git log --oneline

Output (measured 2026-09-09):

cf71c45 portpeek 0.1.0 — first public release

How to read it: notice the habit of including .gitignore from the very first commit (Step 87). The version number 0.1.0 is a conventional notation meaning "still early, but published."

3-3. README and License — Giving It a Front Door

Write the README.md like this (the file used in the measurements — each [section] becomes a Markdown subheading like ## Usage scope in the actual file).

# portpeek  ← the file's title (Markdown # heading)

A single-host port scanner for your own lab. Pure Python with no external
dependencies (standard library only).

[⚠️ Usage scope]
Use this tool only on "systems you own or targets you have explicit
permission for." Unauthorized scanning may violate applicable laws
(computer-misuse / telecommunications regulations, etc.).

[Installation]
All you need is Python 3.10+. Download the single file and run it — no setup.
    git clone https://github.com/your-username/portpeek.git
    cd portpeek

[Usage]
    python portpeek.py 127.0.0.1                # scan 1-1024 (default)
    python portpeek.py 127.0.0.1 --ports 22,80  # specific ports only
    python portpeek.py 127.0.0.1 --ports 1-100  # a range
    python portpeek.py 127.0.0.1 --timeout 1.0  # adjust the timeout

[Options]  ← organized as a table
    --ports   default 1-1024   format: 22,80,443 or 1-100
    --timeout default 0.5      wait time per port (seconds)

[License]
MIT — see the LICENSE file.

The LICENSE file gets the full MIT text — search for "MIT License" and the full text comes right up, and GitHub adds it with one button when you create the repository (screen example — choose MIT under "Choose a license"). Just change the year and name in the file.

git add README.md LICENSE
git commit -m "docs: add README and MIT license"
git log --oneline

Output (measured 2026-09-09):

46852fe docs: add README and MIT license
cf71c45 portpeek 0.1.0 — first public release

3-4. Publishing to a Remote — push

With GitHub, create a repository and connect its address (same as Step 87, screen example). Today’s measurement uses a local bare repository.

Input (local simulation):

cd ..
git init --bare portpeek.git
cd portpeek
git remote add origin ../portpeek.git
git push -u origin main

Output (measured 2026-09-09):

branch 'main' set up to track 'origin/main'.
To ../portpeek.git
 * [new branch]      main -> main

How to read it: this repository is now "public" (to the whole world with GitHub, to a remote in the bare simulation). One thing to do right after publishing — open the repository URL from another device or an incognito window and check "how it looks to others." Does the README appear on the front page? Did a file like .env get uploaded by mistake?

3-5. The First Issue Arrives — The Mixed-Input Bug (Actually Discovered!)

This is what happens when you publish. We’ll turn a bug actually discovered during this chapter’s measurement prep into an issue, verbatim. Example issue body:

Issue #1: mixing a range and a list in --ports crashes it

Running:
python portpeek.py 127.0.0.1 --ports 20-25,80,443
produces the error below.

ValueError: invalid literal for int() with base 10: '25,80,443'

The README's examples show both forms, but it seems mixing them doesn't work.

Verify it yourself (measured 2026-09-09 — it really reproduces):

$ python portpeek.py 127.0.0.1 --ports 20-25,80,443
target: 127.0.0.1 / ports: 20-25,80,443
Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: '25,80,443'

How to read it: parse_ports() only checks whether - is present, so it can’t parse a mixed input like 20-25,80,443. When an issue arrives, a maintainer’s first action is "confirm the reproduction." A good issue (reproduction command + full error text) is half the fix by itself.

3-6. The Contributor’s Fix — Receiving It as a Branch

The contributor (the second-clone role) fixes this issue and sends it over. Here’s the contributor-side procedure.

Input:

cd ..
git clone portpeek.git contributor
cd contributor
git checkout -b fix-port-range

Fix parse_ports() to parse mixed input.

def parse_ports(text):
    ports = []
    for part in text.split(","):
        if "-" in part:
            a, b = part.split("-", 1)
            ports.extend(range(int(a), int(b) + 1))
        else:
            ports.append(int(part))
    return ports

Confirm the fix works, then push.

python portpeek.py 127.0.0.1 --ports 20-22,80
git add portpeek.py
git commit -m "fix: support mixed range+list input in --ports (#1)"
git push -u origin fix-port-range

Output (measured 2026-09-09 — works correctly after the fix):

target: 127.0.0.1 / ports: 20-22,80
  [OPEN] 22
To .../portpeek.git
 * [new branch]      fix-port-range -> fix-port-range

How to read it: the (#1) at the end of the commit message is a conventional notation pointing to the issue number. On GitHub this number links automatically to the issue. The contributor doesn’t touch main directly — they propose via a branch. That is the essence of a PR.

3-7. The Maintainer’s Review and Merge — Completing the Issue Cycle

Return to the original repository (maintainer role), review the incoming branch, and merge it.

Input:

cd ../portpeek
git fetch origin
git merge --no-ff origin/fix-port-range -m "merge: #1 mixed port range+list input support"
git push origin main
git log --oneline

Output (measured 2026-09-09):

45b2056 merge: #1 mixed port range+list input support
538558c fix: support mixed range+list input in --ports (#1)
46852fe docs: add README and MIT license
cf71c45 portpeek 0.1.0 — first public release

How to read it: --no-ff is the option that records the fact "this was a contribution" in history as a merge commit. See how the log shows fix (contributor) and merge (maintainer) stacked side by side — those two lines are the fossil of community collaboration. Finally, run it once more on main to confirm, and the cycle closes with a closing comment on the issue: "Merged into main. Thank you!" (GitHub screen example — the Merge pull request button plays this role).

Why do this: you’ve just moved up one step, from "a person who made a tool" to "a person who received and managed a contribution." This experience — confirming a reproduction, reviewing a fix, merging, giving thanks — is the whole of open-source maintaining, and it’s the basis for writing "open-source maintainer" on your résumé instead of just "open-source contributor."


4. Missions & Exercises

Mission — Publish Your Tool and Run the Contribution Cycle

  1. Pick one of the tools you built in this book and clean it for publication — remove hardcoded paths, add argparse argument handling, tidy error messages (if you have no tool, proceed with today’s portpeek)
  2. Write a README with the six sections (intro / usage scope / installation / usage / options / license) and a LICENSE file
  3. Publish to a GitHub (or local bare) repository, and check "how it looks to others" from a different session
  4. Create a contributor clone and push one improvement as a branch (a bug fix, an added option, a typo fix — anything)
  5. Review and merge it in the original repository, and confirm the fix+merge pair with git log --oneline

Exercises

Exercise 1. Among the README’s six sections, which one is unique to security tools, and what problems arise without it?

Exercise 2. Explain the legal status of public code without a license file, and state the MIT license’s three conditions (permission / obligation / disclaimer).

Exercise 3. Explain why a contributor sends a branch instead of pushing straight to main, together with the maintainer’s review procedure.

Exercise 4. What is this chapter’s answer to the embarrassment of "is this code good enough to publish?" And what effect does a first release have on the next code?


Answers & completion criteria · expand/collapse

5. Model Answers & Completion Criteria

Mission Model Answer

The flow is exactly the measured sequence of Section 3. A set of verification commands:

git log --oneline        # are commits stacked in the order: first release → docs → fix → merge?
git remote -v            # is origin registered?
python portpeek.py --help  # does the tool explain its own usage?

A README in the six-section structure of 3-3 passes, and the ⚠️ usage-scope section in particular must be present. The improvement contribution can be small — 3-6’s mixed-input fix is today’s model case, and that bug was actually discovered during this chapter’s measurement prep.

How to verify: ① does git log --oneline show the fix and merge commits side by side? ② does the README include usage and output examples? ③ does the LICENSE file exist with name and year filled in? ④ is the public repository free of secret files (.env, etc.)?

Exercise Answers

Answer 1. The ⚠️ usage-scope (legal-use statement) section. Without this statement, a scanner or testing tool can be mistaken for — or actually become — a tool for abuse, and liability controversy can reach the distributor. The single line "for your own systems / authorized targets only" is the line that defines the tool’s character.

Answer 2. Public code without a license is, under copyright law, "view only" — use, modification, and redistribution are not permitted. MIT ① permits use, copying, modification, and distribution free of charge, ② imposes the obligation to retain the copyright notice and a copy of the license, and ③ includes a disclaimer that the software is provided "as is" with no liability for damages.

Answer 3. Because a branch is a "proposal" while main is "final." The maintainer fetches the incoming branch, reviews and tests the changes, and only then merges. If contributors could change main directly, the review gate would disappear — this structure is open source’s basic device for safely accepting code from strangers.

Answer 4. Even world-class tools had rough first commits. Publishing is something you do during growth, not after completion. Once published, the pressure of "others are reading" improves the code and docs, and incoming issues tell you what to fix next. The first release that beats the embarrassment changes the next code.

Completion Criteria Checklist

  • [ ] I can explain the criterion for choosing a tool to publish (the one you use most)
  • [ ] I can apply the three cleanup conditions (remove paths / argument handling / error messages)
  • [ ] I created a repository with a README filling all six sections
  • [ ] I applied the MIT license
  • [ ] I pushed to a remote repository and checked "how it looks to others"
  • [ ] I completed the cycle of receiving, reviewing, and merging a contribution branch
  • [ ] Mission: I published my tool and completed one contribution merge

6. Common Pitfalls & Fixes

Wall 1. After publishing, I found my computer’s path baked into the code

Symptom: when someone else runs it, they get FileNotFoundError: C:Usersyour-name....

Cause: an absolute path hardcoded during development was left in.
Fix: go back to 3-1’s cleanup criteria — paths come in as arguments or from a config file. Fix it as soon as you find it and commit. These accumulating fixes are the history of version 0.1.0 → 0.2.0.

Wall 2. An issue has no error message

Symptom (screen example): an issue arrives that just says "it doesn’t work."

Cause: novice users don’t know what reproduction information is.
Fix: prepare a maintainer’s standard reply — "Thanks! Could you share the full command you ran, the complete error message, and your Python version?" Today’s 3-5 issue (command + full error text) is the standard of a good issue, and guiding users toward such issues is also a maintainer’s job. You can also put an issue template in the repository.

Wall 3. Merging the contribution branch caused a conflict

Symptom (output example): CONFLICT (content): Merge conflict in portpeek.py

Cause: the same part of main changed while the contributor was working.
Fix: open the conflicted file, choose which side to keep between the <<<<<<<, =======, >>>>>>> markers, then add and commit — the same procedure as in Steps 87–88. With a small tool, it’s often cleaner to ask the contributor to "re-create the branch from the latest main."

Wall 4. I forgot to change the name and year in the LICENSE

Symptom: Copyright (c) 2026 your-name was published as-is.

Cause: the common mistake of uploading a template untouched.
Fix: correct it and commit — done. But take this chance to check: every template file in a public repository (including your-username in the README) is an item to catch in the "how it looks to others" check (3-4) before uploading.

Wall 5. Nobody looks at my repository

Symptom: a week after publishing, no visitors, no issues.

Cause: normal. Open source isn’t read just because it’s published.
Fix: your repository’s first readers are search engines and recruiters. Check again whether the README’s first line says precisely "what this tool is." And there’s a reverse direction too — small PRs like typo fixes and documentation improvements on existing security open-source projects are the fastest way to put your name into the community.


7. Summary

Today’s Concepts

Concept One-line explanation
README The repository’s front door — must be readable in five seconds
Usage-scope statement The section unique to security-tool READMEs — the line of legal use
MIT license Free use + copyright notice + disclaimer
Issue A bug-report/feature-request post — reproduction info is its lifeblood
PR (pull request) A fix proposal sent as a branch — the separation of proposal and final
Maintaining The maintainer’s work of receiving, reviewing, and merging contributions

Today’s Commands

Command What it does
git init --bare name.git A practice remote repository (GitHub substitute)
git push -u origin main The first public push
git clone address folder The contributor role’s second clone
git checkout -b branch-name Create a branch for a contribution
git fetch origin Fetch an incoming contribution
git merge --no-ff branch Merge while leaving the contribution in history

The Core Instinct

Today’s central sentence is this — the heart of good open source is documentation more than code. If code is skill, the README is the translation of that skill, the license is the invitation, and issue response is hospitality.

And the embarrassment of "is this good enough to publish?" is the same door standing in front of everyone’s first release. Even world-class tools had rough first commits. Publishing is something you do during growth, not after completion — and today you opened that door, with a repository you made yourself and one bug you actually found and fixed.


Once every box is checked, Step 323 is complete.

ONE STEP FURTHER

Finished this lesson?

Check the completion criteria, then mark your progress.

Something wrong with this page or a link? Let us know.
Next