Reporting & Professional Practice · beginner · ~10 min

Ethics, legality, and knowing when to stop

**What you will learn** - Explain the four non-negotiable duties of authorized testing: authorization, scope, do-no-harm, and data protection — and why "reachable" never means "authorized." - Recognize concrete **stop conditions** (a prior real breach, a production-safety risk, drifting out of scope) and follow the correct escalation path immediately. - Handle sensitive data ethically: minimize access, avoid exfiltration, and store or destroy evidence per the engagement rules. - Apply **coordinated (responsible) disclosure** for issues you find outside a formal engagement, such as in open-source software. - Keep a defensible **decision log** that proves you acted lawfully and within scope, and know which events to record and which secrets to never write down. - Correct the common misconception that being technically able to do something makes it legal or permitted.

Overview

Security objective: the asset you are protecting here is trust and legality — the client's data and systems, the public who depends on them, and your own legal standing as a tester. The threat is you: an authorized tester who, through carelessness or ambition, causes harm, breaks the law, or destroys the client's trust. By the end of this lesson you will be able to detect the moments where a test crosses an ethical or legal line and stop before damage is done.

This lesson builds directly on your prereq "Authorization, scope, and rules of engagement" (methodology-rules-of-engagement). There you learned what a rules-of-engagement (RoE) document contains — the signed authorization, the in-scope target list, the time window, and the emergency contacts. Here you learn how to live inside those boundaries under pressure: what to do when a genuinely interesting finding sits just outside scope, when you stumble onto evidence that a real attacker got there first, or when a test is about to knock production offline.

Ethics is not a soft add-on to technical skill — it is the thing that makes the skill legitimate. The exact same port scan, password test, or web request is professional security work when it is authorized and in scope, and a computer crime when it is not. The only difference is permission and restraint. Everything in this lesson is about staying on the right side of that line, and being able to prove you did.

Why it matters

In real, authorized work, ethics and legality are the foundation the entire profession stands on. A penetration test is a controlled attack a client invites into their systems because they trust you to stop before you cause harm. That trust is the product. Cross a line and three things happen at once: you can commit a crime under computer-misuse laws (many carry criminal penalties), you can damage the client (deleted data, downed production, leaked customer records), and you can end your own career — one out-of-scope incident follows a tester for a long time.

Clients, insurers, and courts do not take your word that you behaved. They look at the signed authorization, the scope definition, and your logs. A tester who can produce a clean decision trail — "here is the authorization, here is where I stopped, here is who I called and when" — is defensible. A tester who cannot is exposed, even if they meant well.

Knowing when to stop is a senior skill, not a beginner limitation. Junior testers chase every finding to full exploitation; professionals recognize when a proof-of-concept already proves the point, when a finding needs immediate escalation, and when continuing adds risk without adding value. That judgment is what clients actually pay for.

Core concepts

Each duty below is a separate skill. Learn the definition, the plain idea, how it works in practice, when it applies (and when it does not), and the pitfall that trips up beginners.

1. Authorization

Definition: explicit, written permission from someone with the authority to grant it, for specific systems, over a specific time window.

Plain explanation: authorization is the piece of paper that turns "attack" into "test." Without it, everything you do is potentially a crime, no matter how good your intentions.

How it works: the RoE or engagement contract names the authorizing party, the targets, the dates/times, and the permitted techniques. You keep a copy accessible during the test (a "get-out-of-jail" letter) so you can prove authorization on the spot.

When / when not: you act only when covered by current, signed authorization. If the window has expired, or the person who "said it was fine" verbally has no authority to authorize, you do not have authorization.

Pitfall: treating a verbal "sure, go ahead" as authorization. Verbal permission from the wrong person is worthless in court and unfair to the client.

2. Scope — "reachable is not authorized"

Definition: the exact set of systems, addresses, applications, accounts, and techniques you are allowed to touch.

Plain explanation: the network will happily let you reach machines you were never hired to test — a shared cloud tenant, a third-party API, a neighbouring company on the same subnet. Being able to reach a system is a networking fact; being allowed to test it is a legal fact. They are unrelated.

How it works: scope is defined by an allow-list (IP ranges, domains, apps). Anything not on the list is out of scope by default. When in doubt, it is out.

When / when not: stay strictly inside the allow-list. If a valuable finding leads toward an out-of-scope system (a very common situation), you stop at the boundary and ask before crossing.

Pitfall: "scope creep" — following an attack chain across a trust boundary because it is technically possible and exciting. That is exactly how testers end up attacking a third party who never consented.

3. Do no harm

Definition: prefer the least-damaging action that still demonstrates the issue; get explicit sign-off before anything risky.

Plain explanation: you do not need to fully exploit a bug to prove it exists. A proof-of-concept (PoC) — a screenshot, a single benign marker file, a limited read — usually proves the point without breaking anything.

How it works: destructive and disruptive tests (denial-of-service, mass deletion, ransomware-style actions, uploading real malware) are forbidden unless the contract specifically authorizes them, and even then only in agreed windows. You choose the gentlest evidence that a reviewer would accept.

When / when not: demonstrate impact, do not cause it. If proving a finding requires real damage, you stop and describe the impact instead, or negotiate a controlled test.

Pitfall: "just one more step" to make the finding look dramatic. Dumping an entire production database to prove SQL injection is harm, not proof — a single row or a SELECT version() proves it.

4. Protect data

Definition: minimize what real sensitive data you access, never exfiltrate it, and secure or destroy any evidence.

Plain explanation: during a test you may encounter real customer records, passwords, or secrets. You are a temporary, trusted guest. Look only as much as needed to prove the finding, and never copy sensitive data out of the environment.

How it works: Exfiltrate means copying data out of the target to somewhere you control. As a tester you do not do this. Evidence (screenshots, request logs) is stored encrypted, shared over agreed secure channels, and deleted on the schedule in the contract.

When / when not: access the minimum needed to demonstrate the issue. Never take copies home; never post evidence in a public issue tracker or chat.

Pitfall: screenshotting a full table of real Social Security numbers when a single redacted record would have proved the same thing.

5. Stop conditions

Definition: predefined situations where you halt testing and escalate immediately.

Plain explanation: some discoveries are more important than finishing your task. The clearest is finding that a real attacker was already here — signs of a prior breach. That is now an incident, not a test finding.

How it works: on a stop condition you (1) cease the relevant activity, (2) preserve what you have without altering the scene, and (3) contact your designated point of contact using the agreed emergency channel — now, not in the final report.

Common stop conditions: evidence of a prior/ongoing real breach; a critical safety-impacting issue (e.g. medical, industrial, or life-safety systems); an unplanned production outage or instability you caused or risk causing; realizing you have drifted out of scope.

Pitfall: treating a live-breach discovery as "a cool finding for the report" and continuing to poke — which can tip off the attacker, destroy forensic evidence, or make you look complicit.

6. Coordinated (responsible) disclosure

Definition: the ethical process for reporting a vulnerability you found outside a paid engagement — for example in open-source software or a public product.

Plain explanation: finding a bug in software you use does not authorize you to attack anyone running it. Disclosure is how you get the issue fixed without harming users.

How it works: report privately to the owner (a security contact, security.txt, or a bug-bounty program), give reasonable time to fix, coordinate on timing, and do not publish working exploits against systems that are still unpatched.

When / when not: use it for issues found in your own lab, in OSS, or in products with a disclosure program. It does not grant permission to test live third-party systems — reading source code or testing your own local copy is fine; hammering someone's production server is not.

Pitfall: "full disclosure" dumps — publishing a working exploit before a patch exists — which endangers every user of the software.

THREAT MODEL: the authorized test itself as the risk

  ASSETS PROTECTED
    - Client data (customer records, secrets, PII)
    - Client availability (production must keep running)
    - Public / third parties on shared infrastructure
    - Legality + the tester's own standing

  ENTRY POINTS (where harm gets introduced)
    - Tester's tools running against targets
    - A tempting attack chain leading off-scope
    - Sensitive data encountered mid-test
    - A discovered real prior breach

         IN SCOPE (authorized)        |     OUT OF SCOPE (forbidden)
   +-----------------------------+    |   +---------------------------+
   |  Listed IPs / domains /apps |    |   | Neighboring tenant        |
   |  Agreed time window         | == | ==| Third-party API / SaaS    |
   |  Agreed techniques          |    |   | Anything not on allow-list|
   +-----------------------------+    |   +---------------------------+
                                 TRUST BOUNDARY
                    (reachable != authorized; do not cross
                     without new written permission)

  CONTROLS
    - Signed authorization kept on hand
    - Allow-list checked before every new target
    - PoC-not-full-exploit rule
    - Stop-and-escalate on breach/safety/outage
    - Timestamped decision log

Knowledge check

  1. What asset is protected in this lesson, and who is the main threat to it? (Trust/legality/client data and the public; the threat is the tester themselves acting carelessly or out of scope.)
  2. Where is the trust boundary, and what insecure assumption causes testers to cross it? (Between the in-scope allow-list and everything else; the bad assumption is "if I can reach it, I'm allowed to test it.")
  3. You find a web shell and log entries showing an unknown party has been active for weeks. Which log/evidence signals this is a prior real breach rather than your own activity, and what do you do first? (Timestamps and actions that predate your engagement window and don't match your source IP/toolset; stop the relevant activity and escalate to your point of contact immediately.)
  4. Why must even a "harmless" full-database dump only ever happen in an authorized engagement (or a lab), never against a random reachable server? (No authorization = potential crime and real harm; reachability grants no permission.)

Syntax notes

This is a professional-practice lesson, so the "syntax" is the structure of an authorization check and a decision-log entry — the artifacts that keep you defensible. Below is a lab-safe template you can adapt.

BEFORE-YOU-TOUCH CHECK (run mentally + in your notes for every new target)

  [ ] Signed authorization exists and is current (date within window)
  [ ] Target is on the in-scope allow-list (exact IP/domain/app)
  [ ] Technique is permitted (no DoS/destructive unless signed off)
  [ ] I know the emergency contact + channel for stop conditions
  -> If ANY box is unchecked: DO NOT PROCEED. Ask first.
DECISION-LOG ENTRY (append-only; one line per meaningful action/decision)

  2026-07-07T14:03:11Z | src=10.0.0.5(lab) | target=10.0.0.20:8080
    action="read one row to confirm SQLi" | scope=IN | result=confirmed
    decision="PoC sufficient, did not dump table" | corr_id=ENG-42-017

Key points in the annotation:

  • ISO-8601 UTC timestamp (...Z) so events line up with the client's own logs.
  • Source and target identify who did what to which system.
  • scope=IN/OUT forces you to make the boundary decision explicit.
  • corr_id (correlation id) ties this action to the engagement and to any alert the client's monitoring raises.
  • Never write a real password, token, or full sensitive record into the log (see Security & safety).

Lesson

Technical skill without ethics is a liability. A professional tester works inside firm legal and ethical lines. That is what separates the job from a crime.

The non-negotiables

  • Authorization first, always. You need written permission, for systems in scope, within the agreed time window (the rules of engagement from the methodology track). No authorization, no test.
  • Stay in scope. Reachable is not authorized. Out-of-scope systems are off-limits, even when they are trivially accessible.
  • Do no harm. Avoid actions that could damage data or availability. Get explicit sign-off for anything risky. Denial-of-service and destructive tests are usually forbidden. Prefer proof-of-concept over full exploitation.
  • Protect data. Minimize access to real sensitive data. Do not exfiltrate it (copy it out). Store evidence securely, and delete it per the agreement.

Knowing when to stop

  • Stop and report immediately if you find signs of a prior real breach, or a critical safety-impacting issue. Do not keep poking. The client needs to know now.
  • Halt if you risk disrupting production, or if you have strayed toward out-of-scope or illegal territory. Then contact your point of contact.
  • Respect time and cost limits. Knowing when to stop also means not chasing diminishing returns past the value of the engagement.

Disclosure

Some issues turn up outside an engagement, for example in open-source software. For those, follow responsible (coordinated) disclosure:

  • Report privately to the owner.
  • Give them reasonable time to fix the issue.
  • Do not release public exploits against unpatched systems.

Code examples

Because ethics is about decisions, the "code" here is a small, self-contained Python helper you can run in your own lab to enforce the scope and authorization rules before any action. It shows the INSECURE → SECURE → VERIFY shape: first the naive check that lets scope creep through, then the corrected gate, then tests proving it rejects out-of-scope/expired requests and accepts valid ones. No network calls are made — it is pure decision logic.

# WARNING: intentionally vulnerable — use only in a local, isolated, authorized
# lab. Do not deploy.
#
# INSECURE: "can I reach it?" mistaken for "am I allowed?"
import socket

def may_test_insecure(host, port):
    # BUG: reachability is treated as permission. This is the exact
    # "reachable == authorized" fallacy. It ignores scope AND the clock.
    try:
        with socket.create_connection((host, port), timeout=1):
            return True   # WRONG: being able to connect proves nothing
    except OSError:
        return False
# SECURE: authorization + scope + time window are checked explicitly.
# Reachability is irrelevant to permission.
from datetime import datetime, timezone
from ipaddress import ip_address, ip_network

# In real work these come from the signed RoE, not hardcoded.
AUTH_SIGNED = True
SCOPE_ALLOWLIST = [ip_network("10.0.0.0/24")]      # lab range only
WINDOW_START = datetime(2026, 7, 7, 9, 0, tzinfo=timezone.utc)
WINDOW_END   = datetime(2026, 7, 7, 18, 0, tzinfo=timezone.utc)
DESTRUCTIVE_ALLOWED = False

def may_test(host, *, destructive=False, now=None):
    """Return (allowed: bool, reason: str). Fails closed."""
    now = now or datetime.now(timezone.utc)
    if not AUTH_SIGNED:
        return (False, "no signed authorization")
    if not (WINDOW_START <= now <= WINDOW_END):
        return (False, "outside authorized time window")
    try:
        ip = ip_address(host)
    except ValueError:
        return (False, "target is not a literal IP in the allow-list")
    if not any(ip in net for net in SCOPE_ALLOWLIST):
        return (False, "target is OUT OF SCOPE (reachable != authorized)")
    if destructive and not DESTRUCTIVE_ALLOWED:
        return (False, "destructive technique not signed off")
    return (True, "authorized, in scope, in window")
# VERIFY: prove the gate REJECTS bad requests and ACCEPTS good ones.
from datetime import datetime, timezone

IN_WINDOW = datetime(2026, 7, 7, 12, 0, tzinfo=timezone.utc)
LATE      = datetime(2026, 7, 7, 23, 0, tzinfo=timezone.utc)

def check(label, got, expect):
    ok = (got[0] == expect)
    print(f"[{'PASS' if ok else 'FAIL'}] {label}: {got[1]}")
    assert ok, got

# REJECT cases
check("out-of-scope host", may_test("8.8.8.8", now=IN_WINDOW), False)
check("outside time window", may_test("10.0.0.20", now=LATE), False)
check("destructive w/o signoff",
      may_test("10.0.0.20", destructive=True, now=IN_WINDOW), False)

# ACCEPT case
check("in scope + in window", may_test("10.0.0.20", now=IN_WINDOW), True)
print("all checks complete")

Expected output (order preserved; every line PASS):

[PASS] out-of-scope host: target is OUT OF SCOPE (reachable != authorized)
[PASS] outside time window: outside authorized time window
[PASS] destructive w/o signoff: destructive technique not signed off
[PASS] in scope + in window: authorized, in scope, in window
all checks complete

What this teaches: the insecure version answers "can I connect?" — a networking question. The secure may_test answers "am I permitted?" — a legal question — and fails closed (defaults to "no") on anything unexpected, including a hostname it cannot resolve to an allow-listed IP. The verify block is the mitigation-verification step: it demonstrates the gate blocks the three ways testers go wrong (off-scope, off-clock, unauthorized destructive) while still allowing legitimate work.

Line by line

Walkthrough of the SECURE may_test gate and its verification.

  1. now = now or datetime.now(timezone.utc) — the decision is time-aware. Authorization is not permanent; it expires at the window's end. Using UTC (timezone.utc) means your timestamps line up with the client's logs later.
  2. if not AUTH_SIGNED: — the very first gate. No signed authorization ⇒ immediate False. Everything else is irrelevant without this.
  3. if not (WINDOW_START <= now <= WINDOW_END): — even with authorization, acting outside the agreed window is unauthorized. This catches the "I'll just finish tonight" mistake.
  4. ip = ip_address(host) inside try/except — if host is not a literal IP we can place on the allow-list, we refuse rather than guess. This fails closed: ambiguity becomes "no."
  5. if not any(ip in net for net in SCOPE_ALLOWLIST): — the core scope check. The target must fall inside an allow-listed network. 8.8.8.8 is reachable from almost anywhere, yet it is not in 10.0.0.0/24, so it is rejected — the code encodes "reachable ≠ authorized."
  6. if destructive and not DESTRUCTIVE_ALLOWED: — destructive techniques need their own explicit sign-off, checked separately from ordinary in-scope permission.
  7. return (True, ...) — only if every gate passed. The function returns a reason string in all cases so the decision can be written to the log.

Trace of the verify block:

Call AUTH window? in allow-list? destructive ok? Result
8.8.8.8, in-window yes yes no n/a False — out of scope
10.0.0.20, LATE (23:00) yes no False — outside window
10.0.0.20, destructive yes yes yes no False — no sign-off
10.0.0.20, in-window yes yes yes n/a True — permitted

Notice the gate short-circuits at the first failing condition, so the reason string always names the specific rule that stopped you — exactly what you want in a decision log.

Common mistakes

Real mistakes testers make, why they are wrong, the correction, and how to catch them.

1. "I could reach it, so I tested it."

Why wrong: reachability is a networking accident, not permission. Testing an out-of-scope host can be a crime and can hit an innocent third party on shared infrastructure. Corrected: check the allow-list before every new target; stop at the trust boundary and request written permission before crossing. Recognize/prevent: if you cannot point to the target on the scope document, you are out of scope. Build the allow-list check into your workflow (see the may_test gate).

2. Treating a discovered real breach as "a great finding."

Why wrong: continuing to poke can alert the attacker, destroy forensic evidence, or make it look like you caused the incident. It also delays the client's response to a live threat. Corrected: stop the relevant activity, preserve evidence without altering it, and escalate to your point of contact immediately via the agreed channel. Recognize/prevent: ask "do these timestamps/actions predate my window and not match my source?" If yes, it is not you — escalate now.

3. Dumping everything to "prove" a bug.

Why wrong: a full production database dump is harm and a data-protection violation, not evidence. It also creates a copy of sensitive data you now have to protect. Corrected: capture the minimum PoC a reviewer would accept — one redacted record, a SELECT version(), a single benign marker. Recognize/prevent: before running an action, ask "what is the smallest thing that proves this?" If the answer is smaller than what you were about to do, do the smaller thing.

4. Relying on a verbal "go ahead."

Why wrong: verbal permission from someone without authority is unenforceable and unfair to the client. It leaves you undefended if anything goes wrong. Corrected: require signed authorization from someone empowered to grant it, and keep a copy on hand during the test. Recognize/prevent: no signature and no named authorizer on the document ⇒ do not start.

5. Confusing "I decoded it" with "I verified it" (and "scanner passed" with "secure").

Why wrong: these overclaims mislead the client. Decoding a token (e.g. base64-decoding a JWT) reveals its contents but does not verify its signature or prove it is trusted. A clean automated-scanner run means the scanner found nothing — not that the system is secure. Corrected: state findings precisely: "decoded, not signature-verified"; "scanner reported no issues, manual testing still required." Never claim a system is "completely secure" — say "no issues found within this scope and time." Recognize/prevent: reread every claim and ask "does my evidence actually support this exact sentence?"

Debugging tips

When an ethical decision "fails," the symptom is usually uncertainty or an unexpected escalation. Debug it deliberately.

Common failure: "Is this target in scope?" is unclear.

  • Re-read the scope allow-list; match the exact IP/domain/app, not a "close enough" range.
  • If the document is ambiguous, treat it as OUT of scope and ask your point of contact in writing.
  • Question to ask: "Can I point to this exact system on the signed scope document?" If no, stop.

Common failure: the may_test gate rejects something you expected to pass.

  • Print the returned reason string — it names the first failing rule (window, scope, sign-off).
  • Check the clock: is now actually inside WINDOW_START..WINDOW_END? Time-zone/UTC mistakes are the usual cause.
  • Confirm the host is a literal allow-listed IP, not a hostname the gate deliberately refuses.

Common failure: you think you may have caused an outage.

  • Stop testing immediately; do not try to "fix" production yourself.
  • Note the exact time and last action from your decision log.
  • Contact your point of contact via the emergency channel with facts, not guesses.
  • Question to ask: "What was my last action and its timestamp, and could it have caused this?"

Common failure: you are not sure whether a finding is a prior real breach.

  • Compare artifact timestamps and source addresses against your own engagement window and toolset.
  • Do not delete or "clean up" anything — preserve the scene.
  • When unsure, escalate anyway: a false alarm costs a phone call; a missed live breach costs far more.

General debugging question set for any ethical doubt:

  1. Do I have current, signed authorization for this exact system?
  2. Is this the smallest action that proves the point?
  3. If this goes wrong, who do I call, and is that channel ready?
  4. Would I be comfortable if this exact action appeared, timestamped, in a courtroom?

Memory safety

Security & safety — detection, logging, and evidence handling

Ethical testing is only defensible if it is recorded. Your logs are what prove you stayed lawful and in scope, and the client's logs are how they distinguish your authorized activity from a real attacker.

What to log (for every meaningful action/decision):

  • Timestamp in ISO-8601 UTC (e.g. 2026-07-07T14:03:11Z) so it aligns with the client's monitoring.
  • Source (your testing IP/host, marked as the tester) and target (system + port/endpoint).
  • Resource/action — what you did, in plain words ("read one row to confirm SQLi").
  • Result — confirmed / not confirmed / error.
  • Security decision — the explicit call you made ("PoC sufficient; did not dump table"; "target OUT OF SCOPE, did not proceed").
  • Correlation id tying the entry to the engagement and to any client-side alert.

What to NEVER log or store in the clear:

  • Passwords, API keys, tokens, or session cookies (use placeholders like API_KEY=<development-placeholder>).
  • Private keys.
  • Full sensitive records — never a complete PAN (payment card number), full SSN, or unneeded PII. Redact to the minimum needed to prove the finding.
  • Anything you do not actually need for the report.

Which events signal abuse (and should be escalated):

  • Activity in the client's logs that predates or falls outside your authorized window but matches attacker patterns — a sign of a prior real breach.
  • Your own tools reaching addresses outside the allow-list — a sign of scope drift to catch immediately.
  • Sudden service instability correlated with your last action — a possible availability impact.

How false positives arise: your legitimate testing will trip the client's intrusion-detection and fill their logs with "attacks." This is expected. That is exactly why the RoE includes emergency contacts and why you share your source IPs and time window up front — so the blue team can tell your authorized noise from a real intruder. Coordinating this in advance prevents a costly false-alarm incident response and keeps your activity distinguishable from a genuine threat.

Evidence handling: store evidence encrypted, transfer it only over the agreed secure channel, never in public chats or issue trackers, and delete it on the schedule the contract specifies.

Real-world uses

Concrete authorized use case. A retailer hires your firm for a two-week web-application penetration test. The signed RoE lists three domains and one /24 IP range as in scope, a 09:00–18:00 UTC window, and forbids denial-of-service. On day three you find SQL injection in a search box. A junior instinct is to dump the customer table to "prove" it; the professional move is to run SELECT version() plus read one redacted row, screenshot it, log the decision ("PoC sufficient, no bulk extraction"), and move on. Later you notice a web shell with modification times two months before your engagement — a prior real breach. You stop, preserve it, and call the client's emergency contact within minutes. Both moves — the restrained PoC and the immediate escalation — are what the client is paying for.

Best-practice habits.

Habit Beginner Advanced
Validation Check the allow-list before every target Automate a scope-gate (like may_test) into your tooling so off-scope actions are blocked, not just discouraged
Least privilege Access only what a finding needs Use scoped, time-boxed test accounts; avoid touching real user data at all where a synthetic account suffices
Secure defaults Assume out-of-scope until proven in-scope; fail closed Codify "fail closed" everywhere; require a second person to approve any destructive test
Logging Keep a timestamped decision log Correlate your log with the client's SOC so authorized noise is pre-cleared and abuse stands out
Error handling Stop and ask when unsure Rehearse stop-condition drills so escalation is fast and calm under pressure

Outside formal engagements: if you find a bug in open-source software or a public product, use coordinated disclosure — report privately (a security contact, security.txt, or a bug-bounty program), give time to fix, and never publish a working exploit against unpatched systems. Reading source or testing your own local copy is fine; testing someone else's live server is not authorized.

Practice tasks

All tasks are lab-only — run them against systems you own, local containers, or intentionally-vulnerable VMs/CTFs, never third-party systems. Each ends with a defensive conclusion.

Authorization checklist for every task below: (1) the target is a machine you own or a deliberately-vulnerable lab image; (2) it runs on localhost/an isolated network; (3) you have written the scope and window in your own notes before starting; (4) you know how you would "escalate" (here, just log it). Cleanup/reset: delete any marker files you create, revert VM snapshots, and clear evidence per your own retention note when done.

Beginner

Task 1 — Write your own scope gate.

  • Objective: reproduce the may_test idea from scratch.
  • Requirements: a function taking a host and time; return (allowed, reason); check a hardcoded allow-list, a time window, and a "destructive allowed" flag; fail closed on anything unexpected.
  • I/O: input a host string + timestamp; output the boolean + reason.
  • Constraints: no network calls; pure logic.
  • Hints: use ipaddress.ip_network for the allow-list; default unknowns to False.
  • Concepts: authorization, scope, fail-closed.
  • Defensive conclusion: confirm it rejects an off-scope IP and accepts an in-scope one.

Task 2 — Design a decision-log line.

  • Objective: define a single-line log format for tester actions.
  • Requirements: include UTC timestamp, source, target, action, result, security decision, correlation id; explicitly list at least four things you must NEVER put in it.
  • Constraints: no real secrets or PII — use placeholders.
  • Hints: start from the template in Syntax notes; justify each field in one sentence.
  • Concepts: logging, evidence, data protection.
  • Defensive conclusion: write two example lines — one in-scope success, one out-of-scope refusal — proving the format captures the decision.

Intermediate

Task 3 — Add stop-condition detection to your gate.

  • Objective: extend Task 1 so a flagged "prior breach indicator" forces a halt.
  • Requirements: add an input like breach_indicator=True; when set, return (False, "STOP: escalate — possible prior breach") regardless of other checks; write a fake decision log showing the halt + "escalation."
  • Constraints: the halt must override even a perfectly in-scope, in-window request.
  • Hints: check the stop condition first, before scope/window.
  • Concepts: stop conditions, escalation, fail-safe ordering.
  • Defensive conclusion: demonstrate that a valid request is blocked once the breach flag is set, and that your log records the escalation decision.

Task 4 — Minimal-PoC policy.

  • Objective: for three common lab findings (e.g. SQL injection, an exposed admin page, a reflected input), write the smallest proof-of-concept that a reviewer would accept and the harmful action you are deliberately NOT doing.
  • Requirements: a small table: finding | minimal PoC | harmful action avoided | what you'd log.
  • Constraints: PoCs must be non-destructive and lab-only; no real data.
  • Hints: think "one redacted row," "a screenshot," "a version() string."
  • Concepts: do-no-harm, data protection, evidence.
  • Defensive conclusion: note how each minimal PoC still lets the client reproduce and fix the issue.

Challenge

Task 5 — Coordinated-disclosure playbook.

  • Objective: write a one-page playbook for a vulnerability you "find" in a fictional open-source library (all lab/hypothetical).
  • Requirements: steps for private reporting (where to look for a security contact / security.txt / bounty program), a reasonable fix-time expectation, what evidence to include, what you will NOT publish and why, and how to correct the misconception that "finding the bug lets me test other people's live servers."
  • Constraints: no real project names, no working exploit, no real targets — placeholders only.
  • Hints: separate "my local copy (fine to test)" from "someone else's production (not authorized)."
  • Concepts: responsible disclosure, authorization limits, do-no-harm.
  • Defensive conclusion: end with the retest step — how you confirm the fix works once the maintainer patches, and why you still do not attack live installations.

Summary

Main concepts. Authorized testing rests on four non-negotiables — authorization (current, signed, from someone empowered), scope (an allow-list; reachable is not authorized), do no harm (prefer a minimal proof-of-concept over full exploitation), and protect data (minimize access, never exfiltrate, secure or delete evidence). Layered on top are stop conditions (halt and escalate immediately on a prior real breach, a safety-critical issue, an outage, or scope drift) and coordinated disclosure for issues found outside an engagement.

Key artifacts/commands. A before-you-touch check (authorization? in scope? technique permitted? emergency contact known?), a fail-closed scope gate like may_test that answers "am I permitted?" not "can I connect?", and a timestamped decision log (UTC time, source, target, action, result, security decision, correlation id).

Common mistakes. Treating reachability as permission; poking a discovered real breach instead of escalating; dumping whole datasets to "prove" a bug; relying on verbal permission; and overclaiming — decoding is not verifying, a passing scanner is not proof of security, and nothing is ever "completely secure."

What to remember. The difference between professional security work and a crime is permission and restraint, and you must be able to prove both. Log what matters, never log secrets or full sensitive records, coordinate your noise with the client's defenders, and know exactly when — and how — to stop.