🔑 Analogy
A locksmith and a burglar own the same picks. What separates them is a license and a customer who asked them to open the door. In security, the tools are identical on both sides of the law — a scanner is a scanner. The thing that makes you a professional rather than a criminal isn’t the code; it’s authorization: explicit permission to test a defined set of systems. Master that distinction before you touch a single offensive technique.
Authorization is the whole game
The techniques in this track are neutral. Run a port scan against your own lab and you’re learning; run it against a bank and you’ve committed a crime in most countries — under laws like the US Computer Fraud and Abuse Act or the UK Computer Misuse Act — regardless of whether you “meant well” or caused no damage.
So the first skill isn’t technical. It’s knowing your scope:
- Own it or have written permission. A home lab, deliberately vulnerable VMs (DVWA, Metasploitable), a CTF, or a paid engagement with a signed rules of engagement document.
- Stay inside the lines. The scope defines which hosts, which techniques (some forbid denial-of-service or social engineering), and which time windows are allowed.
- Minimize impact. Authorized doesn’t mean reckless — don’t exfiltrate real data, don’t degrade production, stop and report if you find something critical.
The defender’s three mental models
Beyond ethics, three principles shape how you build and break systems:
graph LR A["Attacker"] -->|must pass| L1["Network<br/>segmentation"] L1 --> L2["Authentication"] L2 --> L3["Authorization<br/>(least privilege)"] L3 --> L4["Input validation"] L4 --> L5["Monitoring<br/>& alerting"] L5 --> D["Data"] style D fill:#059669,color:#fff
- Defense in depth. Never rely on one control. Stack independent layers so breaching the firewall still leaves the attacker facing authentication, then authorization, then validation, then detection. The diagram above is one such stack.
- Least privilege. Every component, user, and token gets the minimum access it needs and no more. A compromised service with read-only access to one table is a far smaller disaster than one with admin everywhere.
- Assume breach. Design as if the attacker is already inside. Shrink the blast radius, segment aggressively, and log everything so you can detect and contain. This is the heart of zero-trust.
Encode your rules of engagement in code
A professional habit: make the tool itself refuse to act outside the authorized scope. Here a scanner checks every target against an allowlisted CIDR before it would ever send a packet — a guardrail that turns a promise into an enforced control. This runs here (pure parsing, no network):
package main
import (
"fmt"
"net"
)
// authorizedScope is the ONLY range this tool is permitted to touch.
var authorizedScope = mustCIDR("192.168.56.0/24") // a typical lab network
func mustCIDR(s string) *net.IPNet {
_, n, err := net.ParseCIDR(s)
if err != nil {
panic(err)
}
return n
}
// inScope is the guardrail every action must pass first.
func inScope(target string) bool {
ip := net.ParseIP(target)
return ip != nil && authorizedScope.Contains(ip)
}
func main() {
targets := []string{"192.168.56.10", "192.168.56.250", "8.8.8.8", "10.0.0.1"}
for _, t := range targets {
if inScope(t) {
fmt.Printf("%-15s ✓ authorized — scanning\n", t)
} else {
fmt.Printf("%-15s ✗ OUT OF SCOPE — refusing\n", t)
}
}
}
8.8.8.8 and 10.0.0.1 are refused before any probe is sent. Building this check into your tooling is how you avoid the career-ending mistake of scanning the wrong network.
Responsible disclosure
When you find a real vulnerability in software you don’t own, the ethical path is coordinated disclosure: report it privately to the vendor (look for a security.txt, a security contact, or a bug-bounty program), give them a reasonable window to fix it (90 days is a common norm), and only then coordinate public disclosure. This protects the users running that software while still ensuring the flaw gets fixed and known. Dumping a working exploit publicly with no warning endangers everyone.
⚠️ 'It was only a scan' is not a defense
People underestimate how low the legal bar is. Unauthorized port scanning, credential stuffing, or even automated requests against a site you don’t own can violate computer-misuse law — no exploitation or damage required. Convictions have followed from “just looking.” Treat every action against a system you don’t own or have written permission to test as off-limits. When in doubt, don’t.
🐹 Build the guardrails in
The scope check above is a pattern worth repeating: encode your constraints in the tool. A rate limiter so you don’t hammer a target, a dry-run flag, an allowlist of hosts, structured logging of every action you took (for the engagement report and your own defense). Go makes these trivial — and a tool that can’t color outside the lines is a tool you can trust yourself with.
See also
- Why Go for security — the language’s fit for tooling.
- Building security tools — shipping responsibly, with rate limits and logging.
- Input validation & injection defense — defense in depth at the code level.
- Authentication & authorization — least privilege in practice.
Next: how to actually build and ship a tool — building security tools.
Related topics
Why Go became the language of security tooling — static binaries that drop anywhere, cheap concurrency for scanners, and a crypto/net standard library that covers most of what a tool needs.
sec-foundationsBuilding Security ToolsThe anatomy of a Go security tool — static cross-compiled builds, stripped binaries, embedded assets, and a concurrent worker-pool skeleton with rate limiting and structured logging you can reuse for any scanner.
defenseInput Validation & Injection DefenseThe bug class behind most breaches — why injection happens (mixing data with code), and the structural fixes: parameterized queries, html/template auto-escaping, allowlist validation, and safe path handling.
Check your understanding
Score: 0 / 51. What single thing separates legal security testing from a crime?
The technique is identical whether you're a pentester or an attacker; the difference is permission. Authorized testing means a signed scope (rules of engagement) defining which systems, which methods, and which times are allowed. No authorization = unauthorized access, which is a crime in most jurisdictions (e.g. the US CFAA) regardless of intent or outcome.
2. What is 'defense in depth'?
Defense in depth assumes any one control can fail, so you stack them: network segmentation, then authentication, then least-privilege authorization, then input validation, then monitoring. An attacker who gets past the firewall still faces auth; past auth, still faces authorization checks. Redundant, independent layers.
3. What does 'assume breach' mean as a design principle?
Perimeter-only security fails the moment someone gets in. Assume-breach flips it: grant least privilege so a compromised component can do little, segment so it can't reach everything, and instrument heavily so you detect and contain. It's the foundation of zero-trust architecture.
4. You find a serious vulnerability in a product you use. What is responsible disclosure?
Responsible (coordinated) disclosure reports the flaw privately first — often via a security.txt contact or a bug-bounty program — giving the vendor a fair window (commonly 90 days) to patch before details go public. It protects users while still ensuring the issue is eventually known and fixed. Dropping a 0-day publicly endangers everyone running the software.
5. Why build a 'scope check' into a security tool?
A hardcoded allowlist (e.g. an authorized CIDR) that the tool checks before scanning or attacking prevents the classic disaster of fat-fingering an IP and hitting production or a third party. It encodes your rules of engagement in the tool itself — a professional habit that turns 'I promise I'll be careful' into an enforced control.
Comments
Sign in with GitHub to join the discussion.