25 KiB
SquidC5 Production-Readiness Execution Plan
Goal: Ship Phases A-D as a free, high-quality open-source C5 teamserver.
Repo: https://github.com/SquidSec/SquidC5 (default branch: master)
Status: Alpha 0.1.x → target 1.0.0 after Phase C minimum; Phase D hardens OSS excellence.
Mandatory git cycle (every change)
From SquidSec AGENTS.md + knowledge-base/MEMORY.md and SquidC5 AGENTS.md:
- Checkout and update
master(pull latest). - Create a feature branch named for the change only.
- Write unit tests first (expected behavior).
- Implement code.
- Red-green-refactor until clean and all tests pass locally.
- Push the feature branch only (never direct to
master). - Open a pull request into
master. - Wait for CI - do not merge red.
- Merge when green.
- Start over at step 1 for the next change.
Rules:
- One logical fix/feature per cycle (small PRs).
- Never commit secrets,
data/, tokens, or API keys. - Never weaken MCP rails, Admin AI sanitize,
public_docslock, or empty-CORS default. - Prod deploy remains: merge
master→ CI binaries → deploy Linuxsquidc5binary only. - After each merge: update this plan checklist (
[x]) in a docs PR only if needed; prefer updating in the same PR that lands the work. - Version bumps: patch for A/B hardening, minor for C features,
1.0.0when MVP-prod exit criteria met.
Exit criteria
MVP-prod (claim "production-ready teamserver baseline")
- All Phase A items merged.
- All Phase B items merged.
- Phase C: C01 implant crypto + C02 HTTPS beacons + C03 kill/miss controls + C06 native beacon v1 (at least Linux).
- CI green on
master; release notes honest; threat model updated. - No known P0 security holes from the readiness assessment.
Full OSS C5 (claim "high-quality free OSS C5")
- MVP-prod + remaining Phase C + Phase D.
- Coverage gate + mypy on core + load/fuzz smoke in CI.
- CONTRIBUTING, changelog OPSEC notes, SBOM on releases.
Change catalog
Each Change ID = one git cycle (one branch, one PR).
Branch naming: feature/<id>-short-slug or fix/<id>-short-slug or docs/<id>-short-slug.
Depends-on must be merged first unless noted parallel-safe.
PHASE A — Stop the bleeding (security baseline)
Theme: Internet-facing without embarrassment. No new major product features.
A01 — API rate limiting enforcement
- Branch:
fix/a01-api-rate-limit - Depends on: none
- Tests first:
- Burst over
SQUIDC5_RATE_LIMIT_PER_MINUTEreturns 429. - Authenticated vs unauthenticated buckets (stricter on auth failures).
- Health endpoint either exempt or very high limit (no self-DoS from probes).
- Burst over
- Code:
- Middleware using
Settings.rate_limit_per_minute(today unused inconfig.py). - Per-IP and optional per-token counters; audit high-rate denials.
- Middleware using
- Files (likely):
src/squidc5/main.py,src/squidc5/config.py, newsrc/squidc5/api/rate_limit.py,tests/test_rate_limit.py - PR title:
fix(api): enforce rate_limit_per_minute
A02 — Max body size middleware
- Branch:
fix/a02-max-body-bytes - Depends on: none (parallel with A01)
- Tests first:
- Body over
max_body_bytes→ 413. - Normal API bodies still succeed.
- Body over
- Code: middleware/ASGI check; wire
Settings.max_body_bytes. - Files:
main.py,config.py,tests/test_max_body.py - PR title:
fix(api): enforce max_body_bytes
A03 — Admin-only ops-admin.js gate
- Branch:
fix/a03-ops-admin-js-gate - Depends on: none
- Tests first:
- Non-admin token → 403 on
/api/v1/ops/admin.js(no JS body). - Admin token → 200 + JS.
- Unauthenticated → 401/403.
- Non-admin token → 403 on
- Code: change
Depends(get_auth)to admin scope check; align with AGENTS.md contract. - Files:
api/routes.py,api/deps.py,tests/test_ops_console_gate.py - PR title:
fix(ops): serve admin.js only to admin scope
A04 — Admin token file permissions
- Branch:
fix/a04-admin-token-chmod - Depends on: none
- Tests first:
- After bootstrap,
admin_token.txtmode is0o600(where OS allows).
- After bootstrap,
- Code: write via temp +
chmod/openwith 0o600 (mirrortls/certs.py). - Files:
main.py,tests/test_admin_token_perms.py - PR title:
fix(bootstrap): chmod 0600 admin_token.txt
A05 — Plugin signing secret hardening
- Branch:
fix/a05-plugin-signing-secret - Depends on: none
- Tests first:
- Default hardcoded secret refused when
debug=False/ non-dev. - Secret from env or
data_dirworks; signature verify still passes for known fixtures.
- Default hardcoded secret refused when
- Code: remove usable default in prod;
SQUIDC5_PLUGIN_SIGNING_SECRETor generated once underdata/; document in.env.example. - Files:
plugins/registry.py,config.py,.env.example,tests/test_plugins_signing.py - PR title:
fix(plugins): require non-default signing secret
A06 — Encrypt LLM API keys at rest
- Branch:
fix/a06-llm-keys-at-rest - Depends on: none (careful with DB column already named
api_key_enc) - Tests first:
- Store key → DB blob is not plaintext.
- Load connection → decrypted key works for outbound mock.
- Missing master key → clear error / refuse start when LLMs configured.
- Code: Fernet (or equivalent) with key from env
SQUIDC5_SECRETS_KEY/ data file mode 0600; migrate existing plaintext rows on read/write. - Files:
ai/admin_ai.py,db/store.py,config.py,tests/test_llm_key_encryption.py - PR title:
fix(ai): encrypt LLM API keys at rest
A07 — Docker healthcheck HTTPS
- Branch:
fix/a07-docker-healthcheck-https - Depends on: none
- Tests first: compose config review + CI smoke already uses
curl -k; align compose healthcheck. - Code:
docker-compose.ymlhealthcheckcurl -sk https://127.0.0.1:8443/api/v1/health; Dockerfile if needed. - Files:
docker-compose.yml,Dockerfile, maybe.github/workflows/ci.yml - PR title:
fix(docker): HTTPS healthcheck with TLS default
A08 — CI pip-audit must fail
- Branch:
fix/a08-pip-audit-gate - Depends on: none
- Tests: CI workflow change; fix any current audit findings or pin allowlist with comment.
- Code: remove
|| truefrom pip-audit step; document exceptions only if unavoidable. - Files:
.github/workflows/ci.yml - PR title:
ci: fail on pip-audit findings
A09 — Docs/README TLS URL consistency
- Branch:
docs/a09-tls-url-defaults - Depends on: none (parallel)
- Tests: n/a or link check smoke.
- Code/docs: README, user-guide, deployment, release blurb examples use
https://+ note--insecure/self-signed for lab. - Files:
README.md,docs/*.md, maybeAGENTS.mdquickstart lines - PR title:
docs: align URLs with TLS-by-default
A10 — Real HITL approval queue (or honest demotion)
- Branch:
feature/a10-hitl-approval-queue - Depends on: none (prefer before policy-heavy work)
- Decision in PR: implement server-side queue (preferred) rather than remove HITL.
- Tests first:
- High-risk action without approval → denied / pending.
hitl_approved: truefrom client alone does not bypass.- Admin (or designated approver scope) approves → action proceeds; audit both request and approval.
- Code:
- Table
hitl_requests(or reuse policy store). - API: list/approve/deny pending.
- Policy engine ignores client-asserted approval; checks server record.
- CLI:
sc5 policy hitl list|approve|deny.
- Table
- Files:
policy/engine.py,db/store.py,api/routes.py,cli.py,tests/test_hitl_queue.py - PR title:
feat(policy): server-side HITL approval queue
A11 — Implant channel auth + AEAD (Phase A security critical)
- Branch:
feature/a11-implant-channel-crypto - Depends on: A02 helpful but not required
- Tests first:
- Beacon without valid key/MAC → rejected; no session created.
- Valid sealed check-in → session + task poll works.
- Replay of old ciphertext rejected (nonce/counter/window).
- Generator emits key material only once; server stores hash/wrapped secret.
- Code:
- Per-implant or listener PSK; AEAD (e.g. ChaCha20-Poly1305) over beacon body.
- Wire HTTP + WS paths; DNS label payload MAC where feasible.
- Config:
SQUIDC5_IMPLANT_REQUIRE_AUTH=truedefault on for new listeners; migration note for lab. - Docs: operator flow to mint implant key.
- Files:
implants/,listeners/http_listener.py,listeners/ws_beacon.py,profiles/,payloads/generator.py,api/routes.py,tests/test_implant_crypto.py - PR title:
feat(implant): authenticated AEAD check-in channel - Note: Largest A change; keep PR focused on HTTP beacon first if needed, follow-up A11b for DNS/WS.
A11b — Implant crypto on DNS + WS (if split from A11)
- Branch:
feature/a11b-implant-crypto-dns-ws - Depends on: A11
- Only if A11 scoped to HTTP only.
PHASE B — Teamserver production core
Theme: Survives restart, upgrades, and small-team ops.
B01 — Schema migration framework
- Branch:
feature/b01-db-migrations - Depends on: none (do early in B; blocks many B items)
- Tests first:
- Fresh DB applies all migrations.
- Old DB fixture upgrades without data loss.
schema_versiontable present.
- Code: simple versioned SQL or light migrator in
db/migrate.py; call fromDatabase.connect; no silent column drift. - Files:
db/store.py,db/migrate.py,tests/test_db_migrations.py - PR title:
feat(db): versioned schema migrations
B02 — Listener restore on boot
- Branch:
feature/b02-listener-restore - Depends on: B01 optional
- Tests first:
- Listener row
status=running→ started after app lifespan. - Bind failure → status
error, audit event, no crash loop of whole app.
- Listener row
- Code:
build_state/ lifespan callslisteners.restore_running(). - Files:
main.py,listeners/manager.py,tests/test_listener_restore.py - PR title:
feat(listeners): restore running listeners on startup
B03 — Listener crash supervision
- Branch:
feature/b03-listener-supervision - Depends on: B02
- Tests first:
- Simulated listener task death → restart with backoff; max retries then error status.
- Code: done-callbacks, exponential backoff, metrics counters.
- Files:
listeners/manager.py,tests/test_listener_supervision.py - PR title:
feat(listeners): supervise and restart crashed listeners
B04 — Backup and restore CLI
- Branch:
feature/b04-backup-restore - Depends on: B01 preferred
- Tests first:
backupcreates consistent SQLite snapshot.restoreloads into empty data dir; app reads sessions/tokens.
- Code:
sc5 backup [path],sc5 restore [path]using SQLite backup API; document stop/start guidance. - Files:
cli.py,db/store.py,docs/operator-runbook.md,tests/test_backup_restore.py - PR title:
feat(cli): backup and restore SQLite data
B05 — Deep health vs public health
- Branch:
feature/b05-deep-health - Depends on: none
- Tests first:
- Public
/healthstill minimal. - Authenticated
/health/deep(admin ormetrics:read) returns db/listeners/disk without secrets.
- Public
- Code: new route; never leak tokens/keys.
- Files:
api/routes.py,tests/test_health.py - PR title:
feat(api): authenticated deep health endpoint
B06 — Structured JSON logging
- Branch:
feature/b06-json-logging - Depends on: none
- Tests first:
- With
SQUIDC5_LOG_JSON=true, log line parses as JSON; no token fields.
- With
- Code: configurable formatter; keep default human logs for lab.
- Files:
main.py,config.py,tests/test_logging_format.py - PR title:
feat(ops): optional JSON structured logging
B07 — Audit retention job
- Branch:
feature/b07-audit-retention - Depends on: B01
- Tests first:
- Rows older than
audit_retention_dayspruned on schedule/startup option. - Recent rows kept.
- Rows older than
- Code: honor existing config knob; optional periodic task.
- Files:
audit/,main.py,config.py,tests/test_audit_retention.py - PR title:
feat(audit): enforce audit_retention_days
B08 — Startup config validation
- Branch:
feature/b08-config-validation - Depends on: none
- Tests first:
- TLS cert without key → fail fast.
- Invalid port / empty required public_host when stabilize on → clear error.
- Code: pydantic validators + lifespan preflight.
- Files:
config.py,main.py,tests/test_config_validation.py - PR title:
feat(config): fail-fast startup validation
B09 — systemd unit + binary upgrade runbook
- Branch:
docs/b09-systemd-upgrade - Depends on: B01, B04 (reference them)
- Code/docs:
packaging/squidc5.service,docs/deployment.mdupgrade section (stop → replace binary → migrate → start). - Files:
packaging/,docs/deployment.md - PR title:
docs(deploy): systemd unit and binary upgrade runbook
B10 — Split api/routes.py into routers
- Branch:
refactor/b10-split-api-routes - Depends on: avoid parallel large route PRs; land after A03/A10/A11 or rebase carefully
- Tests first: existing API tests must pass unchanged (behavior-preserving).
- Code:
api/routers/{sessions,tasks,listeners,tokens,ai,ops,...}.py;build_api_routerincludes them. - Files:
api/routes.py→ split,tests/*unchanged expectations - PR title:
refactor(api): split routes into domain routers
B11 — Split listeners/manager.py
- Branch:
refactor/b11-split-listener-manager - Depends on: B02, B03 preferred first
- Tests: listener + shell tests green.
- Code: extract reverse_shell channel, restore/supervise, probe into modules under
listeners/. - PR title:
refactor(listeners): split manager responsibilities
B12 — Split db/store.py access layer
- Branch:
refactor/b12-split-db-store - Depends on: B01
- Tests: migration + store tests.
- Code: domain repos or mixin modules; keep
Databasefacade. - PR title:
refactor(db): split store by domain
B13 — Split cli.py into subcommand modules
- Branch:
refactor/b13-split-cli - Depends on: B04 (backup commands exist)
- Tests: CLI smoke tests / help tests.
- Code:
cli/commands/*.py, thincli.pyentry. - PR title:
refactor(cli): split subcommands into modules
B14 — Team RBAC enforced on sessions/shell
- Branch:
feature/b14-team-rbac - Depends on: B01
- Tests first:
- Operator not on team cannot shell/session interact.
- Admin bypass.
- Handoff grants access.
- Code: enforce in deps/routes using
collab/teams.py; audit denials. - Files:
collab/teams.py,api/deps.py,api/routes.py,tests/test_team_rbac.py - PR title:
feat(collab): enforce team RBAC on session interact
B15 — CONTRIBUTING + PR template
- Branch:
docs/b15-contributing - Depends on: none
- Files:
CONTRIBUTING.md,.github/PULL_REQUEST_TEMPLATE.md, issue templates - PR title:
docs: CONTRIBUTING and PR templates
B16 — Expanded threat model
- Branch:
docs/b16-threat-model - Depends on: A11, A10 (document real controls)
- Files:
docs/threat-model.md(STRIDE, trust boundaries, implant, redirector, HITL) - PR title:
docs: expand threat model for production posture
PHASE C — Real C2 parity
Theme: Operators can run authorized engagements on beacons, not only reverse shells.
C01 — HTTPS-native beacon templates
- Branch:
feature/c01-https-beacon-templates - Depends on: A11
- Tests: generated payload uses
https://when TLS on; verify flag documented. - Files:
payloads/generator.py,implants/generators.py, tests - PR title:
feat(payloads): HTTPS-native beacon generation
C02 — Kill date + max missed check-ins
- Branch:
feature/c02-implant-lifecycle-controls - Depends on: A11
- Tests: expired kill date stops tasking; max misses marks session dead.
- Files: sessions, implants, profiles, tests
- PR title:
feat(implant): kill date and max missed check-ins
C03 — Profile transform subset (malleable depth)
- Branch:
feature/c03-profile-transforms - Depends on: none (profiles exist)
- Tests: encode/decode round-trip; unknown transform safe-fail.
- Code: small transform pipeline (base64, prepend/append, header map) server + generator.
- Files:
profiles/engine.py,profiles/models.py, tests - PR title:
feat(profiles): malleable transform pipeline v1
C04 — SOCKS proxy task type
- Branch:
feature/c04-socks-pivot - Depends on: A11, preferably C06
- Tests: unit protocol framing; integration lab optional.
- Code: task kind + teamserver side listener association; implant support in native beacon when ready.
- PR title:
feat(pivot): SOCKS proxy tasking v1
C05 — File ops task types
- Branch:
feature/c05-file-ops-tasks - Depends on: A11
- Tests: upload/download/ls task schema + beacon handler contract.
- PR title:
feat(tasking): file list/read/write task types
C06 — Native beacon v1 (Linux)
- Branch:
feature/c06-native-beacon-linux - Depends on: A11, C01, C02
- Tests: build script + protocol compatibility tests against teamserver; lab e2e optional in CI.
- Code: Go or Rust agent (pick one stack and stick); check-in, tasking, sleep/jitter; release artifact optional.
- Files: new
agents/linux/(orimplant-native/), CI job, docs - PR title:
feat(implant): native Linux beacon v1 - Note: largest C change; may split protocol client lib vs agent binary.
C07 — Native beacon Windows v1
- Branch:
feature/c07-native-beacon-windows - Depends on: C06
- PR title:
feat(implant): native Windows beacon v1
C08 — Audit integrity chain or SIEM export
- Branch:
feature/c08-audit-integrity - Depends on: B07
- Tests: tamper detection on hash chain; export JSONL.
- Code: HMAC chain per row or signed batches;
sc5 audit export. - PR title:
feat(audit): integrity chain and export
C09 — Lab e2e victim harness
- Branch:
feature/c09-lab-e2e-harness - Depends on: A11, B02
- Tests: compose profile with victim container; scripted beacon + task whoami.
- Files:
tests/e2e/,docker-compose.lab.yml, CI optional job - PR title:
test(e2e): lab victim beacon playbook
C10 — Redirector automation depth
- Branch:
feature/c10-redirector-tier - Depends on: B09
- Code: generate + validate nginx/caddy configs; docs for tiered deploy.
- PR title:
feat(deploy): redirector tier generators and docs
C11 — Runtime profile switch to live implants
- Branch:
feature/c11-runtime-profile-switch - Depends on: C03, C06
- Tests: profile activate pushes next-check-in instructions.
- PR title:
feat(profiles): runtime profile switch for beacons
PHASE D — OSS excellence
Theme: Project quality bar for serious external contributors.
D01 — Coverage gate in CI
- Branch:
ci/d01-coverage-gate - Depends on: none (raise threshold gradually)
- Code: pytest-cov
--cov-fail-under=70then ratchet. - PR title:
ci: enforce coverage minimum
D02 — mypy on core packages
- Branch:
ci/d02-mypy-core - Depends on: none
- Code: mypy
src/squidc5/{auth,policy,config,api}first; expand later. - PR title:
ci: mypy on core packages
D03 — Fuzz DNS/HTTP profile parsers
- Branch:
test/d03-fuzz-parsers - Depends on: C03 helpful
- Code: Hypothesis or atheris smoke in CI job (time-boxed).
- PR title:
test: fuzz DNS and profile parsers
D04 — Load/soak beacon storm test
- Branch:
test/d04-load-beacon-storm - Depends on: A01, A11
- Code: scripted N concurrent check-ins; assert no crash + p95 latency bound in lab.
- PR title:
test: beacon storm load profile
D05 — SBOM on release assets
- Branch:
ci/d05-release-sbom - Depends on: none
- Code: generate SBOM in release job; attach to GitHub Release.
- PR title:
ci: attach SBOM to releases
D06 — Changelog with OPSEC highlights
- Branch:
docs/d06-changelog - Depends on: none
- Files:
CHANGELOG.mdformat; document process in CONTRIBUTING - PR title:
docs: CHANGELOG with OPSEC section template
D07 — Version policy past 0.1.x
- Branch:
docs/d07-support-policy - Depends on: MVP-prod near
- Files:
SECURITY.md,pyproject.tomlversion bump process - PR title:
docs: supported versions and 1.0 policy
D08 — Dead code and deps cleanup
- Branch:
refactor/d08-deps-cleanup - Depends on: B10-B13 preferred
- Code: remove
NotImplementedErrorstubs, unused deps, silent bare excepts in hot paths. - PR title:
refactor: remove dead stubs and harden error paths
Execution order (serialized critical path)
Parallel tracks where noted. Always full git cycle per ID.
WAVE 0 (parallel, start immediately)
A01 rate limit
A02 max body
A03 admin.js gate
A04 admin token chmod
A05 plugin secret
A07 docker healthcheck
A08 pip-audit CI
A09 docs TLS URLs
B15 CONTRIBUTING
WAVE 1 (after Wave 0 merges; parallel)
A06 LLM key encryption
A10 HITL queue
B01 DB migrations
B05 deep health
B06 JSON logging
B08 config validation
WAVE 2 (depends Wave 1)
A11 implant AEAD (HTTP) [critical path]
B02 listener restore
B07 audit retention
B04 backup/restore
WAVE 3
A11b DNS/WS crypto (if split)
B03 listener supervision
B09 systemd + upgrade docs
B16 threat model
C01 HTTPS templates
C02 kill date / max misses
WAVE 4 (refactors after security lands)
B10 split routes
B11 split listeners
B12 split db
B13 split cli
B14 team RBAC
WAVE 5 (C2 depth)
C03 profile transforms
C05 file ops
C06 native Linux beacon [critical path]
C08 audit integrity
C09 lab e2e
C10 redirector
WAVE 6
C04 SOCKS
C07 Windows beacon
C11 runtime profile switch
WAVE 7 (OSS excellence)
D01-D08
RELEASE
tag 0.2.0 after Wave 2 (security+migrations baseline)
tag 0.3.0 after Wave 4 (ops core + RBAC)
tag 0.4.0 after C06 (native beacon)
tag 1.0.0 when MVP-prod exit criteria met
Agent operating procedure (this engagement)
For each Change ID:
git checkout master && git pull origin mastergit checkout -b <branch>- Write failing tests.
- Implement.
pytest -qandruff check src tests(fix failures).git push -u origin <branch>gh pr createwith summary + test plan.- Wait for CI; fix forward on same branch if red.
- Merge via
gh pr mergeonly when green (squash or repo default). - Mark Change ID done in this file in a tiny follow-up only if not updated in the feature PR.
- Proceed to next ID (respect depends-on / waves).
Do not batch unrelated IDs into one PR.
Do not push to master directly.
Do not deploy feature-branch binaries to prod.
Tracking table
| ID | Phase | Status | PR |
|---|---|---|---|
| A01 | A | done | merged |
| A02 | A | done | merged |
| A03 | A | done | merged |
| A04 | A | done | merged |
| A05 | A | done | merged |
| A06 | A | done | merged |
| A07 | A | done | merged |
| A08 | A | done | merged |
| A09 | A | done | merged |
| A10 | A | done | merged |
| A11 | A | done | merged |
| A11b | A | done | merged |
| B01 | B | done | merged |
| B02 | B | done | merged |
| B03 | B | done | merged |
| B04 | B | done | merged |
| B05 | B | done | merged |
| B06 | B | done | merged |
| B07 | B | done | merged |
| B08 | B | done | merged |
| B09 | B | done | merged |
| B10 | B | done | merged |
| B11 | B | partial | supervise extracted |
| B12 | B | partial | migrate+backup split |
| B13 | B | partial | cli_cmds package |
| B14 | B | done | merged |
| B15 | B | done | merged |
| B16 | B | done | merged |
| C01 | C | done | merged |
| C02 | C | done | merged |
| C03 | C | done | merged |
| C04 | C | done | merged |
| C05 | C | done | merged |
| C06 | C | done | merged |
| C07 | C | done | merged |
| C08 | C | done | merged |
| C09 | C | done | merged |
| C10 | C | done | merged |
| C11 | C | done | merged |
| D01 | D | done | merged |
| D02 | D | done | merged |
| D03 | D | done | merged |
| D04 | D | done | merged |
| D05 | D | done | merged |
| D06 | D | done | merged |
| D07 | D | done | merged |
| D08 | D | done | merged |
Count
| Phase | Changes |
|---|---|
| A | 12 (incl optional A11b) |
| B | 16 |
| C | 11 |
| D | 8 |
| Total | ~47 PR cycles |
Out of scope for this plan
- Unauthorized targeting or offensive use guidance beyond authorized lab harnesses.
- Weakening secure defaults for convenience.
- Direct commits to
master. - Prod deploy of non-
masterCI binaries.
Plan created for full Phase A-D execution. Next action: start Wave 0 with A01 (or parallel Wave 0 set one-at-a-time per cycle).