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.4 SessionKeyManager

5.4 SessionKeyManager

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

Issue, scope, and expire short-lived session keys so strategies can sign without re-prompting on every order.

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 strategy signing action
Runs afterUser authorisation grant
Applies toAll active session keys per user and strategy
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

Operational profile

Modes supportedquarantine

2. Purpose

Issue, scope, and expire short-lived session keys so strategies can sign without re-prompting on every order.

3. Why This Bot Matters

  • Session key never expires

    A compromised key allows unlimited signing indefinitely without re-authorisation.

  • Key scope not strategy-bound

    A key issued for one strategy could sign orders for another, violating least-privilege.

  • No emergency revocation path

    A stolen key cannot be neutralised quickly, extending the attack window.

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 session token for V2 APIclob_authYesBind the session key to the V2 ClobAuth domain for order signing.

5. Required Internal Inputs

InputSourceRequired?Use
User-granted session scope (strategy, methods, max size)Admin UIYesDefine the scope of each issued session key.
KillSwitch active flagKillSwitchYesRevoke all session keys immediately if kill switch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_session_lifetime_h8Session age > 0.75 * max_session_lifetime_hSession age >= max_session_lifetime_hMaximum lifetime in hours of an issued session key.
max_calls_per_session1000Call count > 0.8 * max_calls_per_sessionCall count >= max_calls_per_sessionMaximum number of signing calls before the session key is rotated.

7. Detailed Parameter Instructions

max_session_lifetime_h

What it means

Maximum lifetime in hours of an issued session key.

Default

{ "max_session_lifetime_h": 8 }

Why this default matters

8-hour default matches a trading day; keys expire overnight reducing standing exposure.

Threshold logic

ConditionAction
session_age < max_session_lifetime_hAPPROVE — key valid
session_age >= max_session_lifetime_hREJECT — SESSION_KEY_EXPIRED

Developer check

if (session.age_h >= p.max_session_lifetime_h) return reject('SESSION_KEY_EXPIRED');

User-facing English

Your session has reached its maximum lifetime and has expired.

max_calls_per_session

What it means

Maximum number of signing calls before the session key is rotated.

Default

{ "max_calls_per_session": 1000 }

Why this default matters

Limiting calls-per-session prevents a key from being used indefinitely even within its lifetime window.

Threshold logic

ConditionAction
call_count < max_calls_per_sessionAPPROVE
call_count >= max_calls_per_sessionREJECT — SESSION_KEY_EXPIRED (call budget exhausted)

Developer check

if (session.call_count >= p.max_calls_per_session) return reject('SESSION_KEY_EXPIRED');

User-facing English

Your session has reached its signing limit. Please re-authorise.

8. Default Configuration

{
  "bot_id": "sec.session_key_manager",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "max_session_lifetime_h": 8,
    "max_calls_per_session": 1000,
    "scope_per_strategy": true,
    "auto_revoke_on_idle_h": 2
  }
}

9. Implementation Flow

  1. Receive session key request or signing call.
  2. Check KillSwitch; if active, revoke all session keys and REJECT(KILL_SWITCH_ACTIVE).
  3. For new key request: issue key with scope (strategy_id, methods, max_size, expiry=now+max_session_lifetime_h).
  4. For signing call: verify key exists and is not expired (age check).
  5. Verify call_count < max_calls_per_session; if exceeded, REJECT(SESSION_KEY_EXPIRED).
  6. Verify idle duration < auto_revoke_on_idle_h; if exceeded, revoke and REJECT(SESSION_KEY_EXPIRED).
  7. Increment call_count; emit RiskVote(APPROVE).
  8. Log session event to governance audit trail.

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.

// SessionKeyManager
FUNCTION validateSession(signingCall, sessions):
  IF FETCH(internal.killswitch).active:
    FOR session IN sessions.active: session.revoke()
    EMIT RiskVote(DENY, KILL_SWITCH_ACTIVE); RETURN
  session = sessions.get(signingCall.session_id)
  IF session == null:
    EMIT RiskVote(DENY, SESSION_KEY_EXPIRED); RETURN
  // Lifetime check
  age_h = (NOW() - session.issued_at) / 3600
  IF age_h >= params.max_session_lifetime_h:
    session.revoke()
    EMIT RiskVote(DENY, SESSION_KEY_EXPIRED); RETURN
  // Call budget check
  IF session.call_count >= params.max_calls_per_session:
    session.revoke()
    EMIT RiskVote(DENY, SESSION_KEY_EXPIRED); RETURN
  // Idle check
  idle_h = (NOW() - session.last_used_at) / 3600
  IF idle_h > params.auto_revoke_on_idle_h:
    session.revoke()
    EMIT RiskVote(DENY, SESSION_KEY_EXPIRED); RETURN
  session.call_count += 1
  session.last_used_at = NOW()
  EMIT RiskVote(APPROVE)
  LOG(governance.audit, {session_id, call_count})

SDK calls used

  • clob_auth.issue_session_token(scope)
  • internal.killswitch.status()

Complexity: O(1) per call — hash map session lookup

11. Wire Examples

Input — what arrives on the wire

Signing call with active sessioninternal

{
  "intent_id": "int_4d5e6f7a8b9c0d1e",
  "session_id": "sk_4e5f6a7b8c9d0e1f",
  "strategy_id": "strat.sports_model",
  "timestamp_ms": 1746768672000
}

Output — what the bot emits

RiskVote — APPROVE

{
  "vote_id": "sec.session_key_manager.20260509T150000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "session_id": "sk_4e5f6a7b8c9d0e1f",
    "calls_remaining": 958
  },
  "checked_at": "2026-05-09T15:00:00Z"
}

12. Decision Logic

APPROVE

Key exists, within lifetime, call budget not exhausted, and idle time within limit.

RESHAPE_REQUIRED

Not applicable — manager approves or rejects signing calls.

REJECT

Key expired, call budget exhausted, idle timeout exceeded, or KillSwitch active.

WARNING_ONLY

Warn at 75% of lifetime and 80% of call budget.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote_id": "sec.session_key_manager.20260509T150000Z",
  "decision": "APPROVE",
  "reason_code": null,
  "evidence": {
    "session_id": "sk_4e5f6a7b8c9d0e1f",
    "age_h": 2.5,
    "call_count": 42,
    "calls_remaining": 958,
    "scope": "strat.sports_model"
  },
  "checked_at": "2026-05-09T15:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active; all sessions revoked.Immediately return DENY and revoke all sessions.Trading is currently paused.
SESSION_KEY_EXPIREDHARD_REJECTSession key has exceeded lifetime, call budget, or idle timeout.Return DENY; prompt user to re-authorise.Your session has expired. Please re-authorise.
SESSION_BUDGET_WARNWARNSession call count exceeds 80% of max_calls_per_session.Emit warn; notify user to prepare re-authorisation.Your session is nearly at its signing limit.
SESSION_EXPIRY_WARNWARNSession age exceeds 75% of max_session_lifetime_h.Emit warn; notify user.Your session will expire soon.
SESSION_ISSUEDINFONew session key issued successfully.Log issuance event.New session started.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_sec_sessionkeymanager_active_sessionsgaugecountstrategy_idNumber of active session keys per strategy.
polytraders_sec_sessionkeymanager_expirations_totalcountercountreasonTotal session expirations by reason.
polytraders_sec_sessionkeymanager_calls_totalcountercountdecisionTotal signing call decisions.
polytraders_sec_sessionkeymanager_session_age_hhistogramhoursDistribution of session ages at expiry.

Alerts

AlertConditionSeverityRunbook
SessionKeyExpiredHighRaterate(polytraders_sec_sessionkeymanager_expirations_total[5m]) > 5P2#runbook-sessionkey-expiry
SessionKeyNoneActivepolytraders_sec_sessionkeymanager_active_sessions == 0P1#runbook-sessionkey-none

16. Developer Reporting

{
  "bot_id": "sec.session_key_manager",
  "decision": "APPROVE",
  "inputs_used": [
    "session.age_h",
    "session.call_count",
    "session.scope"
  ],
  "checked_at": "2026-05-09T15:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Session key expiredYour session has expired. Please re-authorise to continue trading.
Call budget exhaustedYour session has reached its signing limit. Please re-authorise.
Session revoked — idle timeoutYour session was revoked due to inactivity. Please re-authorise.

18. Failure-Mode Block

main_failure_modeSession key used after expiry because expiry check was bypassed.
false_positive_riskLegitimate session rejected because system clock is slightly ahead of key's issued_at.
false_negative_riskExpired key accepted if clock skew is large and no monotonic check is used.
safe_fallbackIf session store is unreachable, fail-closed: reject all signing calls until store recovers.
required_dependenciesAdmin UI for session scope config, KillSwitch, CLOB auth token service

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
SESSION_LIFETIME_EXCEEDEDSet session.issued_at to now - max_session_lifetime_h - 1User re-authorises.
CALL_BUDGET_EXHAUSTEDSet session.call_count = max_calls_per_sessionUser re-authorises.
KILL_SWITCH_MASS_REVOKESet killswitch.active=trueManual KillSwitch reset; users must re-authorise.

20. State & Persistence

Cold-start recovery

Reload active sessions from persistent store on restart; expired sessions discarded.

21. Concurrency & Idempotency

AspectSpecification
Execution modelasync event loop
Max in-flight1000
Idempotency keyintent_id
Per-call timeout (ms)5
Backpressure strategydrop newest above 1000 in-flight
Locking / mutual exclusionmutex on session call_count increment

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchKillSwitch triggers mass session revocation.DENY(KILL_SWITCH_ACTIVE) and revoke all sessions.

Emits to (downstream consumers)

BotWhyContract
sec.wallet_permission_guardProvides session expiry to permission guard.Expired sessions cause DENY(SESSION_KEY_EXPIRED) in permission guard.
gov.builder_attributionSession issuance and revocation audit log.GovernanceLog entry on each issue/revoke event.

Sibling bots (same OrderIntent)

Used by (auto-aggregated)

5.1 5.5

External services

ServiceEndpointSLA assumedOn failure
CLOB Auth APIhttps://clob.polymarket.com99.9%Fail-closed on unreachable auth endpoint.

23. Security Surfaces

Abuse vectors considered

  • Session token theft enabling signing calls beyond user intent
  • Call count manipulation to reset budget and extend session

Mitigations

  • Session tokens are scoped to a single strategy_id and methods list
  • call_count is server-side authoritative; never trusted from client

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesSession keys are scoped to CTFExchangeV2 V2 EIP-712 domain; ClobAuth token version '1' remains separate.

API surfaces declared

clob_authinternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-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 active session within lifetime and call budgetage_h=2, call_count=100, max_session_lifetime_h=8, max_calls_per_session=1000APPROVE
Reject expired sessionage_h=9, max_session_lifetime_h=8DENY(SESSION_KEY_EXPIRED)
Reject when call budget exhaustedcall_count=1000, max_calls_per_session=1000DENY(SESSION_KEY_EXPIRED)

Integration Tests

TestExpected result
KillSwitch revokes all active sessionsAll signing calls return DENY(KILL_SWITCH_ACTIVE) immediately
New session issued with correct scope after re-authorisationSession scoped to strategy_id with expiry = now + max_session_lifetime_h

Property Tests

PropertyRequired behaviour
Expired sessions never produce APPROVEAlways true
call_count monotonically increases; never decrementedAlways true

27. Operational Runbook

Session key events are routine security hygiene; mass expiry or zero-active alerts require immediate investigation.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
SessionKeyNoneActive
SessionKeyExpiredHighRate

Manual overrides

Healthcheck

GET /internal/health/sessionkeymanager → green if Session store reachable; at least 1 active session for active strategies.; red if Session store unreachable or zero active sessions while strategies running.

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 lifetime, call budget, and idle revocationCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
KillSwitch mass-revoke test fires correctlyFailure injection testPass

Promote to General live

GateHow measuredThreshold
Zero false-positive session expirations in 48h shadowGrafana SessionKeyExpiredHighRate 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