Commit Graph

1202 Commits

Author SHA1 Message Date
CarterPerez-dev 6f994f3756 feat(canary): kubeconfig generator + YAML template
Phase 6 first commit. Generator produces a real-looking kubeconfig
artifact that points kubectl at the canary's /k/{id} endpoint:

  - text/template embedded YAML following the spec §9.5 layout (one
    cluster + one context + one user, all referencing the same
    prod-cluster + svc-backup-reader names for verisimilitude)
  - APIServerURL = baseURL + "/k/" + t.ID (trailing-slash safe via
    strings.TrimRight)
  - Token = t.ID embedded as the user's bearer token — when kubectl
    fires a request, the bearer in the Authorization header is exactly
    this id, giving us a second lookup path beyond the URL path
  - ClusterName = "prod-cluster", UserName = "svc-backup-reader" —
    hardcoded per spec §9.5 lines 1193-1194 (tempting bait names that
    look like a real prod service-account kubeconfig)
  - Artifact.Kind = KindText (different from docx/pdf KindFile — YAML
    is text)
  - ContentType "application/yaml"
  - Filename default "kubeconfig" with the standard *string deref+trim
    pattern from docx/pdf

The template file is named template.yaml.tmpl (not .yaml) so the
repo's pre-commit check-yaml hook does not parse it as pure YAML —
the {{.X}} Go-template tokens are not valid YAML flow mappings and
would fail the parser. .tmpl is the conventional extension for
Go-templated source files; identify-cli (which check-yaml uses to
match files) does not classify .yaml.tmpl as YAML. Future phases
needing templated YAML (envfile recipes, etc.) should follow the
same convention.

Tests parse the rendered output through gopkg.in/yaml.v3 against a
typed schema struct, asserting every field flows through correctly:
APIVersion, Kind, current-context, Clusters[0].name + .cluster.server,
Contexts[0].name + .context.cluster + .context.user, Users[0].name +
.user.token. Plus Filename defaulting subtests, base-URL
trailing-slash trim, subpath preserve, distinct-ids → distinct
outputs, bearer-token-embedded check, ends-with-newline check.

Handler (Trigger) ships in the next commit per spec §9.5 file split
({generator, handler, template}). At this commit kubeconfig.Generator
has Type() + Generate() but no Trigger() — the package compiles in
isolation but does not yet satisfy generators.Generator interface.
The registry wires it in commit 3.

go test -race -timeout=60s ./internal/token/generators/kubeconfig/...
passes.
2026-05-13 14:21:57 -04:00
CarterPerez-dev 4c25db7b1a docs(readme): add docs link for ai-threat-detection
AI Threat Detection row now has the same [Source Code] | [Docs] link
pair as the other advanced projects with learn/ content.
2026-05-13 14:05:16 -04:00
CarterPerez-dev 2856e2444f chore(canary-phase5): pdf generator complete
Phase 5 ships the fourth generator in the canary registry. PDF canary
tokens are produced by in-place byte-replacing a 76-char fixed-width
placeholder in a committed template.pdf — the placeholder occupies a
direct dictionary value in the page's /AA /O /URI action (uncompressed,
not inside any Flate stream), so substitution preserves byte length and
keeps the cross-reference table valid.

Implementation commits in this phase:
  - c9c93332 feat(canary): pdf template (binary blob + build helper)
  - 13cd6cd1 feat(canary): pdf generator (byte-replace fixed-width URI action)
  - 73dd8373 feat(canary): register pdf generator in registry

Deliverables:
  - 477-byte committed template.pdf (sha256 70c63590…7e103), reproducible
    via go run ./cmd/buildpdftemplate -out ... (rebuild produces byte-
    identical output)
  - cmd/buildpdftemplate hand-builds the PDF-1.4 with computed xref byte
    offsets, validates via pdfcpu.api.Validate before writing
  - pdf.Generator: ErrTriggerURLTooLong sentinel, length-preservation
    guard, post-substitution bytes.Contains check, _ underscore padding
    (handoff overrides spec §9.4 line 1150 space padding — see anti-
    relitigation set), defaultFilename fallback for nil/empty/whitespace
    *string Filename
  - 22 top-level tests + 11 IP-precedence subtests + 4 filename subtests
    + 3 trigger-URL subtests = ~40 effective cases under -race. All 5
    spec-required tests present (LengthUnchanged, ContainsTriggerURL,
    PlaceholderRemoved, TooLongURL_ReturnsError, PdfcpuValidates) plus
    template-invariant + boundary + same-position regression guards
  - Registry expanded 3 → 4; pending list shrinks to {kubeconfig,
    envfile, mysql}

Audit outcome (2 agents in parallel per standing pattern):
  - superpowers:code-reviewer: PASS (0 BLOCKER, 0 SHOULD-FIX, 5 NITs all
    flagged by the agent as "not actionable" / "leave as-is")
  - general-purpose spec-adherence vs §9.4 + §8.5: PASS
    (0 BLOCKER, 0 SHOULD-FIX, 2 NITs: N1 was a miscount — pdf has 11 IP
    precedence subcases identical to docx, verified via grep; N2 was the
    Chromium/PDF.js /AA-stripping caveat, sanctioned-deferred to
    learn/04-CHALLENGES.md per the post-Phase-17 learn/ allowance)

No audit-fix commit needed this phase — zero actionable findings. Unlike
Phase 3 (a46fa446) and Phase 4 (10ae118d) which cleared real findings
before rollup, Phase 5 lands clean.

Pre-rollup acceptance gates clean at HEAD 73dd8373:
  - go build ./... + go vet ./... clean
  - go test -race -timeout=60s ./... — all packages pass
  - go test -tags=integration -race -timeout=300s ./internal/token/...
    ./internal/event/... — all pass
  - golangci-lint run ./... → 0 issues.
  - grep -rn "//nolint" --include="*.go" returns nothing (lint discipline
    inherited from Phase 0 supplement preserved)
  - BACKLOG.md open section still _(none)_

Forward state for Phase 6 (kubeconfig generator): different shape from
file-artifact generators — kubeconfig is YAML text + a path-based
/k/{id}/* handler that records kubectl method/path/UA and responds with
a Kubernetes-shaped 403. Trigger pattern departs from the /c/{id}+GIF
convention webbug/slowredirect/docx/pdf share.
2026-05-13 13:58:55 -04:00
CarterPerez-dev d97662ee61 feat(foundations): add foundations tier — three teaching-first Python projects
Adds the foundations tier: three Python projects pitched at someone
who has never written Python before, ramping from a single-file hash
identifier up to the cryptographic boundary of the beginner tier.

Projects
- hash-identifier: identify ~30 hash formats by prefix, length, and
  charset (one file, ~30 tests).
- http-headers-scanner: fetch a URL and grade its security headers
  A–F (httpx + rich + respx, single-file scanner, 13 tests).
- password-manager: Argon2id + AES-256-GCM encrypted CLI vault with
  atomic durable writes and advisory file locking (the hardest
  foundations project — stepping stone into the beginner tier).

Each project ships with
- Heavily-commented source as a teaching aid (foundations-tier override
  of the no-comments rule — explanations targeted at first-time readers).
- A full learn/ folder: Overview, Concepts, Architecture, line-by-line
  Implementation walkthrough, Challenges.
- README with ASCII banner, badges (incl. tier + difficulty), demo
  sessions, command tables, learn-folder index, see-also links.
- install.sh + justfile + uv-managed dependencies.
- pytest + ruff + mypy + pylint config, all linters at 10.00/10.

Also centralizes the docs/ exclude in .gitignore (was a per-project
list of advanced/beginner docs paths) so dev-only audit reports and
plan documents stay local across the whole repo without needing
individual entries.
2026-05-13 13:57:33 -04:00
CarterPerez-dev 73dd837362 feat(canary): register pdf generator in registry
Fourth entry in the registry map. Cardinality bumps 3 → 4; pending list
shrinks to {kubeconfig, envfile, mysql} (3 remaining).

  - registry.go: adds the import + token.TypePDF: pdf.New() entry
  - registry_test.go:
      - TestBuild_RegistersPDF added (mirrors RegistersDocx)
      - TypePDF removed from TestBuild_PendingTypesNotYetRegistered
      - TestBuild_OnlyExpectedTypesPresentInPhase4 renamed → InPhase5,
        cardinality assertion bumped 3 → 4

Pre-audit gate clean at HEAD: go build ./... + go vet ./... + go test
-race -timeout=60s ./... + go test -tags=integration -race -timeout=300s
./internal/token/... ./internal/event/... + golangci-lint run ./... all
pass with 0 issues.
2026-05-13 13:54:33 -04:00
CarterPerez-dev 13cd6cd1a3 feat(canary): pdf generator (byte-replace fixed-width URI action)
Generator runs at Token-create time. Substitutes the 76-byte placeholder
in the embedded template.pdf with the canary trigger URL padded to the
same byte length. Length preservation is critical: the PDF's xref table
indexes objects by byte offset, so anything that shifts bytes after the
catalog corrupts the file.

Algorithm (spec §9.4 lines 1145-1165):

  triggerURL := strings.TrimRight(baseURL, "/") + "/c/" + t.ID
  if len(triggerURL) > 76 → ErrTriggerURLTooLong
  padded := triggerURL + strings.Repeat("_", 76 - len(triggerURL))
  out := bytes.Replace(pdfTemplate, placeholder, padded, 1)

Defensive checks after substitution:
  - len(out) == len(pdfTemplate) (xref offsets stay valid)
  - bytes.Contains(out, triggerURL) (substitution actually happened)

Trigger is a content-copy of webbug's response (sanctioned per Phase
2/3/4 anti-relitigation set): 200 + 43-byte pixel.Clone() + Cache-Control
+ Pragma headers. Nil-token path returns the same artifact shape with no
event (spec §8.5 defense-in-depth). realIP / lastNonEmptyXFF /
optionalHeader are duplicated byte-identically with docx and slowredirect
— Phase 9 collapses this into a middleware helper.

Test suite (~32 cases, mirrors the docx Phase 4 shape plus PDF-specific
structural guards):

  Template invariants (5):
    - placeholder root byte-locatable exactly once (literal, not encoded)
    - full 76-byte placeholder byte-locatable exactly once
    - starts with %PDF- header
    - ends with %%EOF marker
    - pdfcpu.api.Validate passes on the raw template

  Generator behavior (10):
    - Type() is TypePDF, Artifact.Kind is KindFile
    - ContentType is application/pdf
    - Filename defaulting (nil / empty / whitespace / set)
    - trigger URL precedence (trailing-slash trim, subpath preserve,
      distinct ids → distinct outputs)
    - output length == template length (xref preservation)
    - output contains trigger URL
    - placeholder root removed from output
    - URL > 76 chars returns ErrTriggerURLTooLong (errors.Is)
    - boundary URL at exactly 76 chars succeeds (zero padding)
    - api.Validate still passes on substituted output
    - substitution preserves byte offset (URL occupies the same position
      the placeholder did)

  Trigger behavior (~16 incl subtests):
    - response shape identical to webbug (200 + pixel.Clone + headers)
    - records event with token id / source IP / UA / Referer
    - source-IP precedence (CF > XFF rightmost > XRI > RemoteAddr),
      11 subcases mirroring docx + webbug for full symmetry
    - missing UA/Referer → nil pointers
    - per-call body is an independent slice (mutate one, other unchanged)
    - nil-token path returns GIF + nil event (FK-safe)

go test -race -timeout=60s ./internal/token/generators/pdf/... passes.
2026-05-13 13:54:24 -04:00
CarterPerez-dev c9c9333294 feat(canary): pdf template (binary blob + build helper)
Phase 5 first commit. Adds the committed template.pdf binary blob plus
the pure-Go one-shot utility that produced it, matching the docx Phase
4 pattern (cmd/build<format>template/main.go excluded from production).

cmd/buildpdftemplate/main.go assembles a minimal PDF-1.4 by hand:

  - Header: %PDF-1.4 + 4 high-bit bytes to mark as binary
  - Object 1: Catalog → Pages root
  - Object 2: Pages → single Kids ref + Count 1
  - Object 3: Page with /AA << /O << /Type /Action /S /URI /URI (...) >> >>
    where the URI value is the 76-char placeholder
    HONEY_TRACK_URL_PADDED_TO_FIXED_WIDTH______________________________________
  - xref table with computed byte offsets
  - trailer + startxref + %%EOF

The placeholder is a direct dictionary value (NOT inside a stream), so
substitution can be a plain byte-replace at runtime without breaking the
cross-reference table — spec §9.4 line 1133. No FlateDecode anywhere.

pdfcpu (v0.12.1) is used for validation only — the builder calls
api.Validate before writing, and tests will call it again on substituted
output to confirm the PDF stays well-formed.

Verification at HEAD:
  - 477-byte template.pdf
  - sha256: 70c6359016ceb780539f8c4497991b98ff82201e35a38d36090d88856027e103
  - Reproducible: rebuild produces byte-identical output
  - grep -aob HONEY_TRACK_URL → one offset (240)
  - file: PDF document, version 1.4, 1 page(s)
  - api.Validate passes

go.mod adds pdfcpu as a direct dep with its transitive deps via go mod
tidy. golang.org/x/crypto returns to direct (pdfcpu uses it for AES
encryption support).
2026-05-13 13:54:06 -04:00
CarterPerez-dev 13b2604f18 fix(canary): replace //nolint pragmas with explicit error handling
The standing "no //nolint anywhere" rule (handoff anti-relitigation
set) was honored by Phases 2-4 but inherited template code in
core/database.go, middleware/ratelimit.go, and health/handler.go
carried 6 pragmas from the original template import. Replacing each
with proper error handling rather than silencing the linter:

core/database.go
  NewDatabase ping-failure path: propagates db.Close() error via
  multi-%w fmt.Errorf rather than discarding it via _ = db.Close().
  Pattern matches the existing rollback error wrap on line 102.

  InTx + InTxWithOptions panic-recovery defer: rollback failure now
  logs via slog.Error before re-panicking, rather than discarding
  the error via _ = tx.Rollback(). The original error context is
  preserved by the panic; the rollback failure is observable.

  jitteredDuration: switches from math/rand/v2 to crypto/rand +
  math/big. The function runs once per pool init at startup so the
  per-call cost (~microseconds) is irrelevant. Eliminates the gosec
  G404 trigger without a global exclude (handoff anti-relitigation:
  "don't add new excludes for one-off issues"). Adds the missing
  maxJitter <= 0 guard that the rand.Int64N call would have panicked
  on with a base smaller than 7ns.

middleware/ratelimit.go
  writeRateLimitExceeded: json.NewEncoder().Encode error is now
  logged via slog.Error rather than discarded. slog is already
  imported and used elsewhere in the file (line 60).

health/handler.go
  writeStatus: same pattern; adds log/slog import. Brings the file
  in line with the rest of the codebase's slog-everywhere discipline.

After: zero //nolint pragmas anywhere in backend Go code
(grep -rn "//nolint" returns nothing). golangci-lint run ./... reports
0 issues. All unit tests pass under -race; all integration tests pass
under -race -tags=integration with the testcontainer Postgres.

Surfaced by the pre-Phase-5 cross-phase alignment audit. Cleared as
part of finishing the Phase 0 lint-discipline alignment so Phases 5+
inherit a fully consistent codebase.
2026-05-13 13:28:44 -04:00
CarterPerez-dev fdc4144cbc chore(canary): drop unused auth error helpers from core
Auth-template residue in core/errors.go + core/response.go:

  - UnauthorizedError() / ForbiddenError() + Err{Unauthorized,Forbidden}
    sentinels
  - TokenExpiredError() / TokenInvalidError() / TokenRevokedError() +
    Err{TokenExpired,TokenInvalid,TokenRevoked} sentinels
  - core.Unauthorized() / core.Forbidden() response wrappers

Zero call sites anywhere in the codebase. Worse, the Token*Error names
semantically collide with canary tokens (same package, completely
unrelated meaning) — a future-reader trap that would compound once
Phase 9 wires real operator-bearer auth gates on the admin handler.

Generic AppError plumbing (NewAppError, NotFoundError, DuplicateError,
ValidationError, InternalError, RateLimitError, IsAppError, GetAppError)
is preserved — all in active use.

When Phase 9 needs operator-bearer or turnstile-gated responses, helpers
can be reintroduced with names appropriate to the actual auth design
rather than carrying generic JWT-shaped vocabulary forward into a
canary-token namespace.

Surfaced by the pre-Phase-5 cross-phase alignment audit. Cleared per
the fix-in-phase rule. BACKLOG closed section records the resolution.
2026-05-13 13:28:30 -04:00
CarterPerez-dev 31f1fff343 chore(canary): drop unused argon2 password machinery from core
internal/core/security.go was preserved through Phase 0's auth prune
untouched. Audit: zero callers anywhere in the codebase, yet the file
kept golang.org/x/crypto as a direct dep and ran a full Argon2id KDF
on every process start via a package-level init() that pre-computed
a dummy hash "to prevent timing attacks" — wasted boot CPU for an
unused codepath.

Deleted symbols:
  - HashPassword, VerifyPassword, VerifyPasswordWithRehash
  - VerifyPasswordTimingSafe + dummyHash init()
  - decodeHash, needsRehash internal helpers
  - GenerateSecureToken, GenerateRefreshToken
  - HashToken, CompareTokenHash

go mod tidy demotes golang.org/x/crypto from direct to indirect
(pgx/v5 still pulls it transitively).

Surfaced by the pre-Phase-5 cross-phase alignment audit. Cleared as
part of finishing Phase 0's auth prune rather than logged as deferred
work. BACKLOG closed section records the item + resolution.
2026-05-13 13:28:17 -04:00
CarterPerez-dev 0e47274525 chore(canary-phase4): docx generator complete
Phase 4 ships the third generator in the canary registry. docx canary
tokens are produced by zip-patching a committed template.docx — the
HONEY_TRACK_URL placeholder in word/footer2.xml's INCLUDEPICTURE field
is replaced at Generate time with the canary's /c/{id} trigger URL.
Per-entry zip Method (STORE/DEFLATE) is preserved so Word/LibreOffice
still opens the document. Trigger is shared with webbug (spec §9.3
line 1118): 200 + 43-byte transparent GIF + cache headers; nil-token
returns the same response per spec §8.5 defense-in-depth.

Implementation commits:
  f36cce9a feat(canary): docx template (binary blob + build helper)
  82bb3a9f feat(canary): docx generator (zip-patch INCLUDEPICTURE)
  5884f986 feat(canary): register docx generator in registry

Audit (two agents in parallel; spec §9.3 + §8.5):
  superpowers:code-reviewer        PASS — 0 BLOCKER / 0 SHOULD-FIX / 3 NIT
  general-purpose (spec adherence) PASS — 0 SPEC-VIOLATION, 6/6 invariants MATCH

Cleared in-phase via 10ae118d (fix(canary-phase4): address audit nits
before rollup):
  N1 — added 'RemoteAddr loopback IPv6 [::1]:9999' + 'XFF IPv6 rightmost'
       subtests to docx generator_test.go for symmetry with webbug
  N2 — renamed shadowed cErr to hErr in patchTemplate's loop body
  N3 — newDocxToken helper sets Memo/Type matching webbug's pattern;
       not actionable (stylistic positive)

Decisions baked in this phase (anti-relitigation):
- Spec §9.3 Filename example (`if t.Filename == ""`) does not compile
  against the actual schema — Token.Filename is *string per entity.go:52.
  resolveFilename safely deref's, trims, and falls back to 'Document.docx'
  for nil / empty / whitespace pointers.
- Spec example does not propagate rc.Close() after io.ReadAll — the
  implementation does, ordered so a read error wins over a close error
  (required by .golangci.yml errcheck.check-blank: true).
- cmd/builddocxtemplate is a pure-Go OOXML builder rather than the
  spec's suggested LibreOffice/python-docx workflow. Matches the
  Phase 5 cmd/buildpdftemplate precedent in the implementation plan.
  Lives at cmd/builddocxtemplate (excluded from the production binary).
- cmd helper uses filepath.Clean(out) before os.OpenFile to satisfy
  gosec G304 on an operator-supplied -out flag — no //nolint pragma
  (banned by project rule).

State after Phase 4:
  Registered generators: 3 (webbug, slowredirect, docx)
  Pending generators:    4 (pdf, kubeconfig, envfile, mysql)
  BACKLOG.md open section: still empty
  Pre-audit gate: BUILD/VET/unit/integration/lint clean across backend

Manual smoke (Task 4.6) is operator-deferred: open a generated docx
in LibreOffice with a network monitor and confirm the GET to /c/{id}
fires when the document opens. Not blocking phase rollup.

Phase 5 next: cmd/buildpdftemplate + pdf generator with byte-padded
HONEY_TRACK_URL_PADDED_…_76 placeholder in an /AA /O /URI action.
2026-05-13 03:19:54 -04:00
CarterPerez-dev 10ae118d53 fix(canary-phase4): address audit nits before rollup
Phase 4 audit (2 agents) returned PASS with three NITs. N1 + N2 cleared
in-phase per the standing fix-in-phase rule. N3 was stylistic-consistency
positive and not actionable.

N1 — generator_test.go IP-precedence subtests: added two missing cases
present in webbug's identical suite (the realIP helper triplet is byte-
identical to webbug, so this is symmetry rather than coverage):
- "RemoteAddr loopback IPv6 strips brackets and port" → "[::1]:9999"
- "XFF IPv6 rightmost" → "198.51.100.1, 2001:db8::dead"

N2 — generator.go patchTemplate loop: the second `cErr := ...` shadowed
nothing but reused the name from the close-error block earlier in the
same iteration. Renamed the header-create variant to `hErr` for clarity.

Audit verdicts:
- superpowers:code-reviewer: PASS (0 B / 0 S / 3 N)
- general-purpose spec-adherence vs §9.3 / §8.5: PASS (0 violations,
  6/6 invariants MATCH, BACKLOG.md still empty)
2026-05-13 03:19:13 -04:00
CarterPerez-dev 5884f98616 feat(canary): register docx generator in registry
Phase 4 Task 4.5 — adds token.TypeDocx: docx.New() to registry.Build.

Test updates:
- TestBuild_RegistersDocx: new, mirrors the webbug + slowredirect
  registration tests
- TestBuild_PendingTypesNotYetRegistered: drops TypeDocx from the
  pending list (4 remain: pdf, kubeconfig, envfile, mysql)
- TestBuild_OnlyExpectedTypesPresentInPhase3 →
  TestBuild_OnlyExpectedTypesPresentInPhase4: cardinality assertion
  bumped from 2 to 3
2026-05-13 03:15:00 -04:00
CarterPerez-dev 82bb3a9f14 feat(canary): docx generator (zip-patch INCLUDEPICTURE)
Phase 4 Task 4.3+4.4 — docx Generator that produces a tracked Word
document by embedding template.docx and runtime-substituting the
HONEY_TRACK_URL placeholder in word/footer2.xml with the per-token
trigger URL. Preserves per-entry zip Method (STORE/DEFLATE) so
Word/LibreOffice still opens the result; non-footer entry bodies are
byte-identical to the template.

Generate returns {Kind: KindFile, Filename: t.Filename ?? "Document.docx",
Content: <patched zip>, ContentType: wordprocessingml MIME}. The
spec §9.3 reference snippet uses `t.Filename == ""` which won't
compile against the real schema — Token.Filename is *string. The
generator dereferences safely via resolveFilename, trimming and
falling back to the default for nil/empty/whitespace pointers.

Trigger mirrors webbug exactly per spec §9.3 line 1118: 200 + 43-byte
transparent GIF via pixel.Clone() + cache-control no-store + pragma
no-cache. Nil-token returns the same response with nil event
(spec §8.5 — no token-existence enumeration). realIP /
lastNonEmptyXFF / optionalHeader helpers are copied from webbug per
the standing rule (sanctioned duplication until Phase 9 middleware
extraction).

24 test cases including the six prescribed by implementation plan
§4.3 (OutputIsValidZip, FooterContainsTriggerURL,
FooterDoesNotContainPlaceholder, OtherEntriesUnchanged,
PreservesCompressionMethods, ReturnsGIFLikeWebbug) plus surrounding
correctness/regression coverage (type, kind, content type, filename
defaulting incl. whitespace, trigger-URL trailing-slash + subpath +
uniqueness, source-IP precedence with 9 subtests mirroring webbug,
GIF body independence per Trigger call, nil-token defense).

Also fixes pre-existing lint debt in cmd/builddocxtemplate from
commit f36cce9a: errcheck on the deferred f.Close (now propagates
via named return) and gosec G304 on os.OpenFile (now filepath.Clean
on the operator-supplied -out path). Rebuilding template.docx after
this change produces byte-identical output (verified via sha256sum).
2026-05-13 03:14:12 -04:00
CarterPerez-dev f36cce9a01 feat(canary): docx template (binary blob + build helper)
Phase 4 Task 4.1+4.2 — pure-Go OOXML template builder under
backend/cmd/builddocxtemplate/. Run:

  go run ./cmd/builddocxtemplate \
    -out ./internal/token/generators/docx/template/template.docx

produces a minimal valid .docx (5 zip entries, mixed STORE/DEFLATE
compression) whose word/footer2.xml carries an INCLUDEPICTURE field
referencing the literal placeholder string HONEY_TRACK_URL with the
\\d switch (forces fetch-on-open, no local cache).

The committed template.docx is the embedded source for the Phase 4
docx generator (next commit). Mixed compression methods in the template
make Task 4.3's TestGenerate_PreservesCompressionMethods a meaningful
regression guard — a hardcode-DEFLATE generator would now mismatch on
[Content_Types].xml + word/_rels/document.xml.rels (both STORE).

cmd/builddocxtemplate is excluded from the production binary (separate
cmd/ subdir from cmd/canary).
2026-05-13 03:04:36 -04:00
Carter Perez e9533cdd4d
Merge pull request #229 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/api-rate-limiter/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in /PROJECTS/advanced/api-rate-limiter
2026-05-13 02:42:27 -04:00
Carter Perez f700aac1ad
Merge pull request #227 from CarterPerez-dev/dependabot/go_modules/PROJECTS/intermediate/secrets-scanner/github.com/go-git/go-git/v5-5.19.0
chore(deps): bump github.com/go-git/go-git/v5 from 5.18.0 to 5.19.0 in /PROJECTS/intermediate/secrets-scanner
2026-05-13 02:42:15 -04:00
Carter Perez 07f7b3e07f
Merge pull request #225 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/ai-threat-detection/backend/gitpython-3.1.50
chore(deps): bump gitpython from 3.1.47 to 3.1.50 in /PROJECTS/advanced/ai-threat-detection/backend
2026-05-13 02:42:01 -04:00
Carter Perez 6de5a3b83d
Merge pull request #224 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/api-rate-limiter/python-multipart-0.0.27
chore(deps): bump python-multipart from 0.0.26 to 0.0.27 in /PROJECTS/advanced/api-rate-limiter
2026-05-13 02:41:38 -04:00
Carter Perez d0783c481e
Merge pull request #223 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/bug-bounty-platform/backend/mako-1.3.12
chore(deps): bump mako from 1.3.11 to 1.3.12 in /PROJECTS/advanced/bug-bounty-platform/backend
2026-05-13 02:41:25 -04:00
Carter Perez 95d9cd4202
Merge pull request #222 from CarterPerez-dev/dependabot/uv/PROJECTS/beginner/c2-beacon/backend/python-multipart-0.0.27
chore(deps): bump python-multipart from 0.0.26 to 0.0.27 in /PROJECTS/beginner/c2-beacon/backend
2026-05-13 02:41:07 -04:00
Carter Perez 3b076ab4ba
Merge pull request #221 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/bug-bounty-platform/backend/python-multipart-0.0.27
chore(deps): bump python-multipart from 0.0.26 to 0.0.27 in /PROJECTS/advanced/bug-bounty-platform/backend
2026-05-13 02:40:54 -04:00
Carter Perez fb33c8e810
Merge pull request #220 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/encrypted-p2p-chat/backend/mako-1.3.12
chore(deps): bump mako from 1.3.10 to 1.3.12 in /PROJECTS/advanced/encrypted-p2p-chat/backend
2026-05-13 02:40:47 -04:00
Carter Perez db3cf3d2f1
Merge pull request #219 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/encrypted-p2p-chat/backend/python-multipart-0.0.27
chore(deps): bump python-multipart from 0.0.26 to 0.0.27 in /PROJECTS/advanced/encrypted-p2p-chat/backend
2026-05-13 02:40:39 -04:00
Carter Perez 430c16d63e
Merge pull request #230 from CarterPerez-dev/dependabot/uv/PROJECTS/beginner/c2-beacon/backend/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in /PROJECTS/beginner/c2-beacon/backend
2026-05-13 02:40:31 -04:00
Carter Perez bacc629091
Merge pull request #226 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/encrypted-p2p-chat/backend/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.2 to 2.7.0 in /PROJECTS/advanced/encrypted-p2p-chat/backend
2026-05-13 02:40:19 -04:00
Carter Perez 6f9ce8df4a
Merge pull request #228 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/bug-bounty-platform/backend/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.2 to 2.7.0 in /PROJECTS/advanced/bug-bounty-platform/backend
2026-05-13 02:40:11 -04:00
Carter Perez 5b8032754a
Merge pull request #231 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/ai-threat-detection/backend/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in /PROJECTS/advanced/ai-threat-detection/backend
2026-05-13 02:40:02 -04:00
Carter Perez ae8d15ab95
Merge pull request #232 from CarterPerez-dev/dependabot/uv/PROJECTS/beginner/keylogger/urllib3-2.7.0
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in /PROJECTS/beginner/keylogger
2026-05-13 02:39:54 -04:00
Carter Perez 4b8875016b
Merge pull request #217 from CarterPerez-dev/dependabot/uv/PROJECTS/advanced/ai-threat-detection/backend/mako-1.3.12
chore(deps): bump mako from 1.3.11 to 1.3.12 in /PROJECTS/advanced/ai-threat-detection/backend
2026-05-13 02:39:47 -04:00
dependabot[bot] 413e9fd40e
chore(deps): bump urllib3 in /PROJECTS/beginner/keylogger
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 17:31:33 +00:00
CarterPerez-dev fe7d641af8 chore(canary-phase3): slowredirect generator complete
Phase 3 of 18. Adds the second token type: a slowredirect generator
that returns a `KindURL` artifact pointing at `/c/{id}`, serves an
embedded HTML+JS fingerprinting page on trigger, and exposes a
sibling `POST /c/{id}/fingerprint` endpoint that enriches the most
recent matching event's `Extra` JSONB via the existing
event.Repository.AttachFingerprint method. Generator interface
contract from Phase 2 unchanged; registry adds the second entry.

Implementation commits:
  71823f5d feat(canary): slowredirect generator (Generate + Trigger + template)
  6dd520f9 feat(canary): slowredirect fingerprint endpoint
  be13d2f1 feat(canary): register slowredirect generator in registry
  a46fa446 fix(canary-phase3): address audit findings before rollup

Audit outcome — TWO agents (superpowers:code-reviewer + general-purpose
spec adherence) dispatched in parallel. Code-reviewer returned FAIL
with 1 blocker + 4 should-fix + 7 nits; spec-adherence returned PASS-
with-1-violation. Both findings cleared in commit a46fa446 per the
fix-in-phase rule:

  • BLOCKER B1 — template.html used `{{...|js}}` which Go's html/
    template double-escapes in a JS string-literal context. The
    on-the-wire output became `\\u003D` / `\\u0026` (double backslash),
    which JS parses as literal backslash-u-text rather than `=` / `&`.
    Realistic URLs like `?utm_source=newsletter&utm_medium=email`
    were silently corrupted before `window.location.replace`. The
    bug was faithfully transcribed from spec §9.2 lines 1031, 1038,
    1045 — the spec's parenthetical claiming `js` is a custom func
    is factually wrong. Resolution: drop `| js` from both JS-context
    actions; rely on html/template's built-in jsstrescaper.
    Regression test TestTrigger_DestinationRoundtripsThroughJSStringDecode
    asserts the absence of `\\u003D` / `\\u0026` for a real URL.
    Spec doc (local-only) updated to match.

  • SPEC-VIOLATION #1 — nil-token Trigger returned `404 + "Not Found"`,
    contradicting spec §8.5 ("token not found | 200, ... deliberately
    do NOT 404") and §8.5 line 964 ("/c/* endpoints never return JSON
    errors"). 404 is a token-enumeration oracle. Resolution: nil-token
    branch renders the same template with a benign decoy destination
    ("/") and returns 200 + text/html + the full CSP/cache header set.
    Body is byte-shape indistinguishable from a valid response except
    for the embedded destination URL. The `(nil event, &resp, nil
    error)` return shape from the Phase 2 anti-relitigation rule is
    preserved. New test
    TestTrigger_TokenNotFound_HasSameResponseShapeAsValidToken
    asserts the indistinguishability invariant.

  • SHOULD-FIX S1 — extractDestination allowlists `http(s)://`
    (case-insensitive); other schemes (`javascript:`, `data:`,
    `file:`, `vbscript:`, scheme-relative `//host`, no-scheme) now
    return the new exported ErrInvalidDestinationScheme. Phase 9's
    upstream `validate:"url"` is necessary but not sufficient (the
    validator's `url` tag accepts any scheme). 8-case
    TestGenerate_RejectsDangerousDestinationSchemes + 4-case
    TestGenerate_AcceptsHTTPAndHTTPSSchemes added.

  • SHOULD-FIX S3 — fingerprint handler silently 204s any
    Content-Type that doesn't start with `application/json` (the
    embedded template always sends JSON). Rejects before the
    MaxBytesReader read. Test added.

  • SHOULD-FIX S4 — added integration tests for oversize body
    (128 KiB → 204, Extra untouched) and empty token id
    (`/c//fingerprint`).

  • SHOULD-FIX S2 — nonce-based CSP suggested as a senior-bar polish.
    Deferred: this is a spec deviation (§9.2 line 1047 specifies
    `script-src 'unsafe-inline'` verbatim) and crosses into Phase 9
    territory (global SecurityHeaders middleware). Re-evaluate when
    Phase 9 wires the trigger handler.

  • Nits N1-N7 reviewed; the realIP/lastNonEmptyXFF duplication
    is sanctioned per the Phase 2 anti-relitigation note (Phase 9
    middleware promotion is the cleanup point). Other nits either
    correct as-is or deferred to Phase 9+ tasks.

Pre-rollup gate clean: `go build ./...`, `go vet ./...`,
`go test -race -timeout=60s ./...` (unit), `go test -tags=integration
-race -timeout=300s ./internal/token/... ./internal/event/...`, and
`golangci-lint run ./...` (0 issues). BACKLOG.md open section
remains empty.

Registry cardinality: 2 of 7 token types live (webbug + slowredirect).
Five remain: docx (Phase 4), pdf (Phase 5), kubeconfig (Phase 6),
envfile (Phase 7), mysql (Phase 8).
2026-05-12 06:47:46 -04:00
CarterPerez-dev a46fa4465e fix(canary-phase3): address audit findings before rollup
Phase 3 audits returned one BLOCKER and one SPEC-VIOLATION plus two
should-fix items; all cleared in-phase per the no-rot rule.

BLOCKER — template.html double-escaped JS-context interpolations.
Go's html/template auto-escapes {{.X}} in <script> string-literal
position via its built-in jsstrescaper. Piping through the explicit
`| js` (JSEscaper) re-ran the escaper, producing `\\u003D` /
`\\u0026` on the wire (double backslash). JS parses `\\u003D` as a
literal backslash followed by "u003D" text rather than `=`, so any
realistic destination URL — e.g.
https://news.example.com/article?utm_source=newsletter&utm_medium=email
— was corrupted into
https://news.example.com/article?utm_source=newsletter&utm_medium=email
before being passed to window.location.replace. The unit tests
previously missed it because every test destination was an
escape-free string. Fix: drop `| js` from both JS-context actions
in template.html; the contextual auto-escaper alone produces single-
backslash `=` which JS correctly decodes. Spec §9.2 line 1031
and 1038 (and the explanatory paragraph 1045) are factually wrong
about `| js` being a custom template func — it is `JSEscaper`, and
adding it on top of the built-in contextual JS string escaper is
pure double-escape harm. Spec docs (local-only, not committed)
amended accordingly. Added
TestTrigger_DestinationRoundtripsThroughJSStringDecode as a
regression guard against re-introducing the pipeline.

SPEC-VIOLATION — nil-token Trigger returned `404 + "Not Found"`,
breaking spec §8.5 ("token not found | 200, ... deliberately do NOT
404") and §8.5 line 964 ("/c/* endpoints never return JSON errors").
A 404 on a non-existent token ID lets scanners enumerate which IDs
are real. Fix: nil-token branch now renders the same template with a
benign decoy destination ("/") and returns 200 + text/html with the
full CSP override and cache headers. Response is byte-shape
indistinguishable from a valid-token response except for the
destination URL embedded in the script. Added
TestTrigger_TokenNotFound_HasSameResponseShapeAsValidToken to
assert the indistinguishability invariant. The `(nil event, &resp,
nil error)` return shape from the handoff anti-relitigation is
preserved.

SHOULD-FIX S1 — extractDestination now allowlists `http://` and
`https://` (case-insensitive) and returns the new
ErrInvalidDestinationScheme sentinel otherwise. The noscript
meta-refresh sits in an HTML-attribute context where html/template
does NOT recognize `url=...` as a URL context, so `javascript:`,
`data:`, `file:`, `vbscript:` and scheme-relative `//example.com`
would otherwise render verbatim and follow on JS-disabled clients.
Phase 9's `validate:"url"` on input is necessary but not sufficient
(the validator's `url` tag accepts any scheme). Added
TestGenerate_RejectsDangerousDestinationSchemes (8 cases) and
TestGenerate_AcceptsHTTPAndHTTPSSchemes (4 case-variants).

SHOULD-FIX S3 — fingerprint handler now silently 204s on any
Content-Type that does not start with "application/json". The
embedded template is the only legitimate caller and always sends
JSON; this rejects adversarial probes before the MaxBytesReader
read. Added
TestFingerprintHandler_WrongContentType_Returns204AndDoesNotTouchEvent.

SHOULD-FIX S4 — added integration tests for the oversize-body cap
(128 KiB body returns 204, Extra untouched) and the empty-token-id
guard (`/c//fingerprint`); the latter accepts chi's natural routing
behavior alongside the in-handler 204.

Non-functional cleanups: extracted shared template-render path
into renderWith / renderResponse / renderDecoyResponse to keep the
nil-token + happy-path bodies definitionally identical;
ErrMissingDestination and ErrInvalidDestinationScheme exported so
callers (and tests) can ErrorIs them; TestGenerate_RejectsMissing*
tightened to use require.ErrorIs(tc.wantErr) instead of bare
require.Error.

Pre-rollup gate (go build / vet / test -race unit+integration /
golangci-lint) is clean.
2026-05-12 06:46:45 -04:00
CarterPerez-dev be13d2f1c4 feat(canary): register slowredirect generator in registry
Phase 3 wiring: registry.Build now maps token.TypeSlowRedirect to
slowredirect.New() alongside the existing webbug entry. The pending-
types regression guard sheds SlowRedirect from its list (5 remain:
docx, pdf, kubeconfig, envfile, mysql) and the cardinality assertion
moves from "exactly one generator" to "exactly two".
2026-05-12 06:34:12 -04:00
CarterPerez-dev 6dd520f966 feat(canary): slowredirect fingerprint endpoint
POST /c/{id}/fingerprint handler that decodes a JSON fingerprint body
and merges it into the most recent event's Extra JSONB for the same
(token_id, source_ip) tuple within a 30s window via the existing
event.Repository.AttachFingerprint method. Decoupled from the concrete
repository through a small FingerprintAttacher interface so future
event.Service can swap in without touching this package.

Per spec §9.2 the fingerprint POST is enrichment-only: a missing match
(event.ErrNotFound), an invalid JSON body, an empty body, and a body
exceeding 64 KiB all silently return 204. Only unexpected repository
errors are logged via slog. The token id is read from chi.URLParam("id")
so the route mounts naturally under the existing trigger router in
Phase 9.

Three integration tests against testcontainers cover the happy-path
JSONB merge, the no-matching-event path (silent 204), and the
invalid-JSON path (204 plus stored Extra left untouched).
2026-05-12 06:34:04 -04:00
CarterPerez-dev 71823f5de2 feat(canary): slowredirect generator (Generate + Trigger + template)
HTML+JS browser-fingerprint redirect page rendered via html/template:
Generate reads metadata.destination_url off the token row and returns
KindURL with the trigger URL + persisted destination; Trigger renders
the embedded template (noscript meta-refresh + inline fetch posting
the fingerprint, then window.location.replace), returns the page with
Content-Security-Policy override allowing inline script + connect-src
'self', and emits the event capturing token id / source IP / UA /
Referer.

Nil-token Trigger returns nil event (FK guard) plus a 404 HTML body,
matching the established defensive-rendering pattern from webbug.
Destination_url is extracted defensively (missing/empty/whitespace
all surface ErrMissingDestination). realIP precedence copied from
webbug pending the Phase 9 middleware promotion.

19 unit-test assertions cover Type, Generate's URL+destination output,
five missing-destination edge cases, XSS neutralization in two
injection contexts (attribute escape + script-closing escape), CSP
override + cache headers, event-recording metadata, nil-token contract,
and per-call response independence.
2026-05-12 06:33:52 -04:00
CarterPerez-dev 8207b2747b chore(canary-phase2): generator interface + webbug complete
Phase 2 of the 18-phase plan. Lands the plugin scaffolding (Generator
interface, shared transparent-pixel package, generator registry) and the
first concrete generator (webbug). Phase 2 is intentionally the smallest
phase — subsequent phases add slowredirect, docx, pdf, kubeconfig,
envfile, and mysql generators against the same Generator interface.

Implementation commits (oldest → newest):

  6cc724c1 feat(canary): Generator interface for token-type plugins
  0b6e586e feat(canary): shared 43-byte transparent GIF for image triggers
  9c0ad749 feat(canary): webbug generator (Generate + Trigger)
  bb95f745 feat(canary): generator registry (token.Type → Generator)
  c39b56b9 fix(canary): clear pre-audit lint debt + migrate golangci config to v2
  1a240952 fix(canary-phase2): address audit findings before rollup

Deliverables vs. plan:

- Generator interface (spec §6.2): Type/Generate/Trigger contract with
  ArtifactKind url|file|text|connection_string, Artifact discriminated
  by Kind, TriggerResponse value type so generators stay pure.
- Pixel package: 43-byte canonical GIF89a literal exposed via
  pixel.Clone() (immutable source, independent slice per call) +
  pixel.Len() helper + ContentType const.
- Webbug generator (spec §9.1 + §8.5): Generate returns KindURL
  artifact with baseURL/c/{id} (trailing slash trimmed). Trigger returns
  200 + image/gif + 43-byte pixel + no-store cache headers.
  realIP precedence: CF-Connecting-IP > X-Forwarded-For (rightmost
  non-empty) > X-Real-IP > net.SplitHostPort(RemoteAddr). Handles
  IPv4/IPv6 (including bracketed form). Empty XFF entries (trailing
  comma, all-empty list) fall through to the next source. Nil token
  returns nil event + non-nil GIF response — the contract is explicit
  in the return shape, so the future Phase 9 handler cannot accidentally
  insert an event row with empty TokenID (which would fail the events
  → tokens FK anyway).
- Generator registry: lives in backend/internal/token/generators/registry/
  (sub-package, NOT the generators package itself) to break the
  generators → webbug → generators import cycle the implementation plan
  inadvertently created. registry.Config + registry.Registry +
  registry.Build. Phase 2 registers webbug only; cardinality test
  asserts exactly 1 and enumerates the 6 future types as absent.
- Cross-cutting cleanup (c39b56b9): pre-audit gate found Phase 0/1
  golangci-lint debt the prior phase audits didn't catch. Per
  fix-in-phase rule, cleared all 14 (5 errcheck, 6 golines, 1 funlen
  via run() → run+initTelemetry+mountRouter+gracefulShutdown split,
  1 govet shadow, plus the 1 Phase-2-introduced funlen-on-test that
  the broken .golangci.yml v1-syntax exclude-rules failed to suppress
  under golangci-lint v2). Also migrated .golangci.yml to v2 schema
  (linters.exclusions.{paths,rules}) and added gosec G706 to excludes
  (false-positive log-injection on slog structured-logging call sites).

Phase 2 audit — TWO agents in parallel per standing pattern:

  superpowers:code-reviewer       → PASS (9 findings, all cleared in-phase
                                          by commit 1a240952)
  general-purpose (spec-adherence)→ PASS (every spec contract item
                                          verified; 3 known deltas
                                          accepted: Type() refined to
                                          token.Type, registry moved to
                                          subpackage to break cycle,
                                          optionalHeader bridges
                                          plan-vs-schema pointer types)

Findings cleared in-phase (from code-reviewer):

  MEDIUM — realIP fallback returned "IP:port"; now net.SplitHostPort
  MEDIUM — XFF rightmost empty entry skipped fallback; now walks right-
           to-left and falls through cleanly
  MEDIUM — pixel.TransparentGIF was exported mutable []byte; now
           unexported + pixel.Clone() per call
  LOW    — nil token returned non-nil event with empty TokenID; now
           returns nil event (explicit contract)
  LOW    — no IPv6 coverage in IP precedence tests; added 3 IPv6 cases
           + XFF IPv6 + port-less RemoteAddr
  NIT    — gracefulShutdown swallowed shutdown errors; now errors.Join

Accepted as-is (NIT, Phase 3+ concern):

  NIT — Artifact discriminated-union may want sealed-interface
        refactor once non-URL kinds land
  NIT — Registry exposed bare map (acceptable: read-only post-Build,
        Go memory model permits concurrent map reads with no writes)
  NIT — registry.Build accepts but ignores its Config (signature
        reserved for future stateful generators like mysql)

Forward-looking notes for Phase 3 (NOT deferred items, just heads-up):

  - Phase 3's main.go wire-up cannot copy spec §6.3's
    `generators.BuildRegistry(cfg.Canary)` verbatim; the call is now
    registry.Build(registry.Config{...}) and the cfg.Canary field will
    need to be declared on config.Config.

Deferred items: NONE. BACKLOG.md open section remains empty.

Quality gates (final, post-fix):

  go build ./...                      OK
  go vet ./...                        OK
  go test -race ./...                 PASS (4 packages, 20 unit tests)
  go test -tags=integration -race     PASS (token + event integration,
                                            ~12s testcontainers)
  golangci-lint run ./...             0 issues

Audited: PASS.
2026-05-12 02:57:29 -04:00
CarterPerez-dev 1a240952c7 fix(canary-phase2): address audit findings before rollup
Two audit agents (code-reviewer + spec-adherence) both returned PASS but
flagged substantive findings. Per fix-in-phase / no-backlog-rot, clearing
every MEDIUM + LOW in-phase.

Findings addressed:

MEDIUM — realIP RemoteAddr fallback returned "IP:port" verbatim, where
the spec wants the bare IP (geoip + downstream parsing assume host-only).
Now uses net.SplitHostPort with raw-string fallback if not host:port.
Adds 4 RemoteAddr cases: IPv4 strips port, IPv6 bracket form strips
brackets+port, loopback IPv6, and the port-less raw fallback.

MEDIUM — realIP XFF branch accepted whatever rightmost-comma-split
produced, so headers like "198.51.100.1, " (trailing comma) yielded
"" and skipped XRI/RemoteAddr entirely. Extracted into lastNonEmptyXFF
which walks right-to-left and falls through cleanly. Adds two cases:
trailing-comma falls through, all-empty entries fall through.

MEDIUM — pixel.TransparentGIF was an exported mutable []byte; any caller
could clobber it process-wide (mutating one response's body would affect
every subsequent webbug trigger). Renamed to unexported transparentGIF
+ exposed pixel.Clone() and pixel.Len(). Webbug now calls pixel.Clone()
per trigger; new test TestTrigger_ResponseBodyIsIndependentCopyPerCall
verifies two triggers return independent slices.

LOW (reviewer-escalated to HIGH-for-Phase-3) — Trigger on nil token
returned a non-nil event with empty TokenID. Since events.token_id is a
NOT NULL FK to tokens.id, the Phase 3 handler could not have persisted
that event anyway, and the contract was implicit (would have required
an inline comment, which the no-comments rule forbids). Now: nil token
in → nil event, non-nil response out. Contract is explicit in the
return shape. Test asserts evt is nil for nil-token path.

LOW — no IPv6 coverage in IP-precedence tests. Added IPv6 cases for
XFF rightmost and three RemoteAddr forms (bracketed, loopback, no-port).

NIT — gracefulShutdown swallowed every shutdown error, returning nil
unconditionally. Now collects with errors.Join so callers can detect
partial-shutdown for telemetry/exit-code purposes.

Audits accepted as-is (NIT, not blocking):
- Artifact discriminated-union shape (Phase 3+ concern when other Kinds
  are produced)
- Registry returning bare map (read-only post-Build; concurrent reads
  are safe by Go's memory model)
- Build(_ Config) ignoring its arg (signature reserved for future
  stateful generators)

Quality verified post-fix: build/vet/lint clean (0 issues),
14 webbug tests + 6 pixel tests + 4 registry tests PASS under -race,
integration tests unchanged.
2026-05-12 02:56:29 -04:00
CarterPerez-dev c39b56b9af fix(canary): clear pre-audit lint debt + migrate golangci config to v2
Pre-audit gate (`golangci-lint run`) at the start of Phase 2 surfaced 15
issues that should have been zeroed before Phase 1 rollup. Per the
fix-in-phase rule (no backlog rot), clearing everything before Phase 2
audit agents run on a green tree.

Config:
- .golangci.yml: migrate `issues.exclude-rules` and `issues.exclude-dirs`
  to v2 syntax (`linters.exclusions.{rules,paths}`); test-file funlen/
  dupl/goconst exclusion now actually applies under golangci-lint v2.10
- .golangci.yml: add G706 to gosec excludes — false-positive log-injection
  reports on slog structured-logging call sites (slog separates message
  from kv args, immune to log-line injection by construction)

errcheck (5):
- cmd/canary/main.go: `_ = telemetry.Shutdown(...)` → log on error
- internal/token/repository.go: `defer stmt.Close()` → log on close error
- internal/event/repository.go: same as token
- internal/testutil/postgres.go: `_ = pgContainer.Terminate(...)` and
  `_ = db.Close()` → t.Logf on cleanup error (use distinct err names to
  avoid govet shadow)

funlen (1):
- cmd/canary/main.go: split `run` into `run` + `initTelemetry` +
  `mountRouter` + `gracefulShutdown` (was 55 statements, now under
  the 50 cap; helpers are individually well under)

govet shadow (1):
- cmd/canary/main.go: migrations check switched from `if err :=` to
  `if err =` (reuses outer err whose value was already consumed) — the
  inner short-decl was lexically shadowing without intent
- cmd/canary/main.go: select-case `err` renamed to `startErr` to avoid
  the same shadow pattern

golines (6, all auto-fixed by `golangci-lint run --fix`):
- main.go, config.go, telemetry.go, event/repository.go, token/dto.go,
  token/repository.go — long lines wrapped to 80-col

Verified post-fix: build OK, vet OK, unit tests PASS under -race,
integration tests PASS under -tags=integration -race (12s testcontainers),
golangci-lint reports 0 issues.
2026-05-12 02:49:56 -04:00
CarterPerez-dev bb95f74579 feat(canary): generator registry (token.Type → Generator)
Lives in its own subpackage to break the otherwise-cyclic import
(generators interface → webbug impl → generators interface): the registry
must import concrete implementations, which themselves import the
generators package for Artifact/TriggerResponse types.

- registry.Config { BaseURL string } (reserved for future stateful generators)
- registry.Registry = map[token.Type]generators.Generator
- registry.Build registers webbug only; subsequent phases append
  slowredirect, docx, pdf, kubeconfig, envfile, mysql

Tests assert webbug present, unknown type returns nil, pending types
are not yet claimed, Phase 2 cardinality is exactly 1.
2026-05-12 02:43:53 -04:00
CarterPerez-dev 9c0ad74973 feat(canary): webbug generator (Generate + Trigger)
- Generate returns KindURL artifact: baseURL/c/{id} (trailing slash trimmed)
- Trigger builds event from CF-Connecting-IP|XFF(rightmost)|XRI|RemoteAddr precedence,
  captures UA + Referer (nil when absent), returns 43-byte GIF response with
  no-store cache headers
- Nil-token path still returns GIF (spec §8.5 defense-in-depth)
- Tests: artifact shape, request metadata capture, IP precedence chain (5 cases),
  nil-pointer mapping for absent headers, GIF response shape + cache headers,
  missing-token path

realIP() lives here temporarily; promotes to middleware in Phase 9.
2026-05-12 02:42:17 -04:00
CarterPerez-dev 0b6e586eb1 feat(canary): shared 43-byte transparent GIF for image triggers
Used by webbug, docx, pdf, envfile triggers. Single source of truth.
Test asserts length, magic bytes, GIF89a trailer, and decodes via image/gif.
2026-05-12 02:40:24 -04:00
CarterPerez-dev 6cc724c19f feat(canary): Generator interface for token-type plugins
- ArtifactKind enum: url, file, text, connection_string
- Artifact struct discriminated by Kind
- TriggerResponse value-typed (handler writes; generator stays pure)
- Generator interface: Type, Generate, Trigger
2026-05-12 02:40:00 -04:00
dependabot[bot] 606b8d93d8
chore(deps): bump urllib3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:59:10 +00:00
dependabot[bot] cd1dac6240
chore(deps): bump urllib3 in /PROJECTS/beginner/c2-beacon/backend
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:55:52 +00:00
dependabot[bot] 0a33fa9735
chore(deps): bump urllib3 in /PROJECTS/advanced/api-rate-limiter
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:55:49 +00:00
dependabot[bot] 1ddc8acf39
chore(deps): bump urllib3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:30:23 +00:00
dependabot[bot] 5ba40b27dc
chore(deps): bump github.com/go-git/go-git/v5
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.18.0 to 5.19.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Changelog](https://github.com/go-git/go-git/blob/main/HISTORY.md)
- [Commits](https://github.com/go-git/go-git/compare/v5.18.0...v5.19.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.19.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:03:40 +00:00
dependabot[bot] 373c7ba487
chore(deps): bump urllib3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 16:00:17 +00:00
CarterPerez-dev 697f4909d7 fix(canary-phase1): clear all post-phase-1 audit observations + header normalization
No 'logged for later' — every non-blocking finding from the Phase 1 audits
is fixed now, in this commit, before declaring the phase truly closed.

Code cleanups (5 items):
1. Database.SQLDB() *sql.DB accessor on core.Database; main.go uses
   db.SQLDB() instead of the awkward db.DB.DB triple-chain.
2. Hoisted list-default literals (50, 20) to package-level
   defaultListLimit consts in token + event repositories. MEMORY.md
   'no magic numbers' rule honored.
3. Moved ptr[T any] helper to internal/testutil/ptr.go as testutil.Ptr;
   removed local copies from token + event repository_test.go.
4. Renamed CHECK constraints in 0001_create_tokens.sql to match spec
   text exactly: chk_token_type → chk_type, chk_alert_channel → chk_channel.
   Spec was the original contract; impl now aligns.
5. Resolved APP_ENVIRONMENT vs ENVIRONMENT divergence per spec §12.1:
   - compose.yml: ENVIRONMENT=production → APP_ENVIRONMENT=production
   - dev.compose.yml: ENVIRONMENT=development → APP_ENVIRONMENT=development
   - config.go envKeyMap: ENVIRONMENT → APP_ENVIRONMENT mapping

Concurrency bug fix (real, not just polish):
- core/migrations.go: added sync.Mutex around goose calls. goose's
  package-level state (SetBaseFS, SetDialect) raced under t.Parallel()
  testcontainers tests. Race detector caught it under -race after
  parallel test counts climbed. Mutex serializes goose invocation
  globally; testcontainer-parallelism otherwise unaffected.

Header normalization (50 files):
- Bulk-normalized every project file's header to canonical
  '©AngelaMos | 2026' (no space, with © glyph, year 2026) per MEMORY.md
  style rule. Eliminated 4 distinct non-canonical variants: '// AngelaMos',
  '// ©AngelaMos | 2025', '// © AngelaMos | 202X', '# AngelaMos'.
- Verified clean: grep returns zero non-canonical headers across the
  whole project tree (excluding gitignored docs/ and node_modules/).

Backlog discipline:
- Created docs/plans/BACKLOG.md with strict format: open items only,
  HIGH/MEDIUM/LOW severity, must-clear-before-ship contract.
- BACKLOG currently has zero open items — every observation was fixed
  here, not deferred. Closed-items section logs what was cleared.

Verification:
- go build ./...                                     clean
- go vet ./...                                       clean
- go test -tags=integration -race ./internal/token/...  11/11 PASS
- go test -tags=integration -race ./internal/event/...   9/9 PASS
- docker compose -f compose.yml config              parses
- docker compose -f dev.compose.yml config          parses
- grep for non-canonical headers                     zero matches
2026-05-10 06:15:26 -04:00