Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerSecurity5.5 KeyRotationReminder

5.5 KeyRotationReminder

Security Guardrail RejectPause PLANNED Spec started capital · Direct P5 · Execution rails pending stub

Nag the user to rotate signing keys on a schedule; prevent key reuse across environments.

v3 readiness

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerSecurity  Security
Bot classGuardrail
AuthorityRejectPause
StatusPLANNED
ReadinessSpec started
Runs beforeAny signing call using a key past its rotation schedule
Runs afterSession key validation
Applies toAll active signing keys per user across environments
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

Operational profile

Modes supportedquarantine

2. Purpose

Nag the user to rotate signing keys on a schedule; prevent key reuse across environments.

3. Why This Bot Matters

  • Signing key never rotated

    A long-lived key that is compromised silently provides unlimited signing authority over time.

  • Same key used across prod and staging

    A staging environment compromise exposes production signing capability.

  • No block on overdue rotation

    Users ignore rotation reminders indefinitely, leaving stale keys in production.

No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.

4. Required Polymarket Inputs

InputSourceRequired?Use
ClobAuth API key registration timestampclob_authYesDetermine when the current signing key was registered to calculate age.

5. Required Internal Inputs

InputSourceRequired?Use
Configured rotate_every_days and block_on_overdue_hAdmin UIYesSchedule and enforcement thresholds for key rotation.
KillSwitch active flagKillSwitchYesBlock all signing during global pause.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
rotate_every_days30Key age > 0.9 * rotate_every_daysKey age > rotate_every_days + (block_on_overdue_h / 24)Number of days before a signing key should be rotated.
require_unique_per_envTrueSame key fingerprint detected in multiple environmentsSame key fingerprint used in prod and non-prod simultaneouslyReject signing if the key fingerprint is shared across environments.

7. Detailed Parameter Instructions

rotate_every_days

What it means

Number of days before a signing key should be rotated.

Default

{ "rotate_every_days": 30 }

Why this default matters

30-day default aligns with industry key management best practices.

Threshold logic

ConditionAction
key_age_d < rotate_every_daysAPPROVE
rotate_every_days <= key_age_d <= rotate_every_days + block_on_overdue_h/24WARN — rotation overdue
key_age_d > rotate_every_days + block_on_overdue_h/24REJECT — KEY_ROTATION_OVERDUE

Developer check

if (key_age_d > p.rotate_every_days + p.block_on_overdue_h/24) return reject('KEY_ROTATION_OVERDUE');

User-facing English

Your signing key is overdue for rotation. Trading is blocked until you rotate it.

require_unique_per_env

What it means

Reject signing if the key fingerprint is shared across environments.

Default

{ "require_unique_per_env": true }

Why this default matters

Unique keys per environment limit blast radius of a staging compromise.

Threshold logic

ConditionAction
key fingerprint unique per envAPPROVE
key fingerprint shared across envs AND require_unique_per_env=trueREJECT — KEY_REUSE_ACROSS_ENV

Developer check

if (p.require_unique_per_env && isKeySharedAcrossEnvs(key)) return reject('KEY_REUSE_ACROSS_ENV');

User-facing English

Your signing key is shared across environments. Please rotate to a unique key.

8. Default Configuration

{
  "bot_id": "sec.key_rotation_reminder",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "rotate_every_days": 30,
    "block_on_overdue_h": 24,
    "require_unique_per_env": true,
    "publish_to_user": true
  }
}

9. Implementation Flow

  1. Receive signing call.
  2. Check KillSwitch; if active, REJECT(KILL_SWITCH_ACTIVE).
  3. FETCH key registration timestamp from ClobAuth.
  4. Compute key_age_d = (now - registered_at) / 86400.
  5. If key_age_d in warning range: emit WARN and continue.
  6. If key_age_d > rotate_every_days + block_on_overdue_h/24: REJECT(KEY_ROTATION_OVERDUE).
  7. If require_unique_per_env: check fingerprint across env registry; if shared, REJECT(KEY_REUSE_ACROSS_ENV).
  8. Emit RiskVote(APPROVE) if all checks pass.

10. Reference Implementation

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.

// KeyRotationReminder
FUNCTION checkKeyRotation(signingKey, envRegistry):
  IF FETCH(internal.killswitch).active:
    EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
  keyInfo = FETCH(clob_auth.key_info(signingKey.fingerprint))
  IF keyInfo == null:
    EMIT RiskVote(DENY, STALE_DATA); RETURN
  key_age_d = (NOW() - keyInfo.registered_at) / 86400
  grace_d = params.block_on_overdue_h / 24
  // Overdue block
  IF key_age_d > params.rotate_every_days + grace_d:
    EMIT RiskVote(DENY, KEY_ROTATION_OVERDUE); RETURN
  // Warning band
  IF key_age_d > params.rotate_every_days * 0.9:
    EMIT warn(KEY_ROTATION_DUE_SOON)
  // Cross-env check
  IF params.require_unique_per_env:
    IF envRegistry.count(signingKey.fingerprint) > 1:
      EMIT RiskVote(DENY, KEY_REUSE_ACROSS_ENV); RETURN
  EMIT RiskVote(APPROVE)
  LOG(governance.audit, {fingerprint, key_age_d})

SDK calls used

  • clob_auth.get_key_info(fingerprint)
  • internal.killswitch.status()

Complexity: O(e) where e = number of environments (small constant)

11. Wire Examples

Input — what arrives on the wire

Signing call with healthy keyinternal

{
  "intent_id": "int_5e6f7a8b9c0d1e2f",
  "key_fingerprint": "ab12cd34",
  "env": "prod",
  "timestamp_ms": 1746768672000
}

Output — what the bot emits

RiskVote — APPROVE

{
  "vote_id": "sec.key_rotation_reminder.20260509T160000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "key_age_d": 12,
    "days_until_block": 19
  },
  "checked_at": "2026-05-09T16:00:00Z"
}

12. Decision Logic

APPROVE

Key is within rotation schedule, unique per environment, and KillSwitch inactive.

RESHAPE_REQUIRED

Not applicable.

REJECT

Key overdue for rotation or shared across environments.

WARNING_ONLY

Warn when key age is within 10% of rotate_every_days.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote_id": "sec.key_rotation_reminder.20260509T160000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "key_fingerprint": "ab12cd34",
    "key_age_d": 12,
    "rotate_every_days": 30,
    "days_until_required_rotation": 18
  },
  "checked_at": "2026-05-09T16:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Immediately return DENY.Trading is currently paused.
KEY_ROTATION_OVERDUEHARD_REJECTSigning key age exceeds rotate_every_days plus grace period.Return DENY; prompt key rotation.Your signing key is overdue for rotation. Please rotate it to resume trading.
KEY_REUSE_ACROSS_ENVHARD_REJECTKey fingerprint detected in multiple environments.Return DENY; require unique key per environment.Your signing key is shared across environments. Please use a unique key.
KEY_ROTATION_DUE_SOONWARNKey age exceeds 90% of rotate_every_days.Warn user; allow signing to continue.Your signing key will require rotation soon.
STALE_DATAINFOClobAuth API unavailable; key age could not be verified.Log; continue with cached data if available.Could not verify key rotation status.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_sec_keyrotationreminder_decisions_totalcountercountdecisionTotal rotation check decisions.
polytraders_sec_keyrotationreminder_overdue_totalcountercountTimes signing was blocked for overdue rotation.
polytraders_sec_keyrotationreminder_key_age_dgaugedaysenvCurrent age of active signing key per environment.

Alerts

AlertConditionSeverityRunbook
KeyRotationOverduepolytraders_sec_keyrotationreminder_key_age_d > rotate_every_daysP1#runbook-keyrotation-overdue
KeyRotationBlockrate(polytraders_sec_keyrotationreminder_overdue_total[5m]) > 0P0#runbook-keyrotation-block

16. Developer Reporting

{
  "bot_id": "sec.key_rotation_reminder",
  "decision": "APPROVE",
  "inputs_used": [
    "clob_auth.key_registered_at",
    "config.rotate_every_days"
  ],
  "checked_at": "2026-05-09T16:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Rotation reminderYour signing key is approaching its rotation deadline. Please rotate it before it expires.
Signing blocked — rotation overdueYour signing key is overdue for rotation. Trading is blocked until you complete the rotation.
Signing blocked — key shared across environmentsYour signing key is shared across environments. Please rotate to a unique key for each environment.

18. Failure-Mode Block

main_failure_modeClobAuth API unreachable so key registration timestamp cannot be retrieved, causing all rotation checks to fail.
false_positive_riskClock skew between ClobAuth and the bot causes a key to appear older than it is, triggering premature block.
false_negative_riskKey registration timestamp cached stale, allowing an overdue key to continue signing.
safe_fallbackIf ClobAuth is unreachable, fail-closed after block_on_overdue_h: block all signing until connectivity restored.
required_dependenciesClobAuth API, Admin UI config, KillSwitch

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
KEY_OVERDUESet key registered_at to now - (rotate_every_days + 2) daysUser rotates key.
KEY_REUSERegister same fingerprint in both prod and staging env registryUser generates separate key for each environment.
CLOB_AUTH_DOWNBlock ClobAuth API endpointAutomatic when ClobAuth recovers.

20. State & Persistence

Cold-start recovery

Re-read from ClobAuth on restart; fail-closed if unavailable after grace period.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight200
Idempotency keyintent_id
Per-call timeout (ms)200
Backpressure strategydrop newest
Locking / mutual exclusionnone

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch gate.DENY(KILL_SWITCH_ACTIVE) short-circuits.
sec.session_key_managerSession key provides fingerprint for rotation check.Session must be active before rotation check runs.

Emits to (downstream consumers)

BotWhyContract
gov.builder_attributionAudit log rotation events.GovernanceLog entry on each check.

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
ClobAuth APIhttps://clob.polymarket.com99.9%Fail-closed after block_on_overdue_h.

23. Security Surfaces

Abuse vectors considered

  • Backdating key registration timestamp to appear younger than actual age
  • Using same key fingerprint across environments to avoid key generation overhead

Mitigations

  • Registration timestamp read from authoritative ClobAuth API, not from client
  • Cross-environment fingerprint check is server-side

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesTracks V2 ClobAuth API key registration timestamps; rotation policy applies to keys used for V2 order signing.

API surfaces declared

clob_authinternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ4-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Approve key within rotation windowkey_age_d=12, rotate_every_days=30APPROVE
Warn when key in 90% of rotation windowkey_age_d=28, rotate_every_days=30APPROVE with WARN
Reject when key overduekey_age_d=32, rotate_every_days=30, block_on_overdue_h=24DENY(KEY_ROTATION_OVERDUE)

Integration Tests

TestExpected result
Key shared across envs triggers rejectDENY(KEY_REUSE_ACROSS_ENV) when same fingerprint in prod and staging
ClobAuth unreachable fails closed after grace periodDENY after block_on_overdue_h elapses without connectivity

Property Tests

PropertyRequired behaviour
key_age_d > rotate_every_days + block_on_overdue_h/24 always DENYAlways true
Shared key fingerprint across envs always DENY when require_unique_per_env=trueAlways true

27. Operational Runbook

KeyRotationOverdue is a P1 security event; trading is blocked until rotation completes.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
KeyRotationBlock
KeyRotationOverdue

Manual overrides

Healthcheck

GET /internal/health/keyrotationreminder → green if All active keys within rotate_every_days; no overdue alerts.; red if Any key older than rotate_every_days + block_on_overdue_h/24.

28. Promotion Gates

A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.

Promote to Shadow

GateHow measuredThreshold
Unit tests pass for overdue block and cross-env reuseCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Overdue injection test fires DENY correctlyFailure injection testPass

Promote to General live

GateHow measuredThreshold
Zero false-positive rotation blocks in 48h shadowGrafana KeyRotationBlock alert history0 false positives

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
Purpose defined✓ done
Required inputs listed✓ done
Parameters defined✓ done
Defaults defined✓ done
Warning thresholds defined✓ done
Hard thresholds defined✓ done
Safe fallback defined✓ done
Structured output defined✓ done
Developer log defined✓ done
Plain-English explanation✓ done
Unit tests defined✓ done
Integration tests defined✓ done
Property tests defined✓ done
Failure-mode block complete✓ done
Reference implementation pseudocode✓ done
Wire examples (input + output)✓ done
Reason codes listed✓ done
Metrics & logs defined✓ done
State & persistence defined✓ done
Concurrency & idempotency defined✓ done
Dependencies declared✓ done
Security surfaces declared✓ done
Polymarket V2 compatibility declared✓ done
Version & migration history declared✓ done
Operational runbook defined✓ done
Promotion gates defined✓ done
Failure-injection recipes defined✓ done