Skip to content

Advisory intelligence contract (pilot)

All Waspada FMS alerts are advisory intelligence. The bank owns any freeze / risk-score decision.

Machine-readable fields (gateway-signed)

FieldPilot valueNotes
dispositionadvisoryactionable requires corroboration_count ≥ CORROBORATION_ACTIONABLE_THRESHOLD (default 3). Unreachable with a single design partner.
client_reported_confidence0–1SDK extraction_confidenceclient-claimed, not gateway-assessed. Signed for integrity only.
mule_keyHMAC-SHA256(consortium_pepper, "waspada-mule-key/v2"|account|bic)Stable identity for dedupe / corroboration. Keyed, not a bare hash — an unkeyed digest of a short account number is GPU-enumerable and would be re-identifiable.
mule_key_epochintegerWhich pepper produced mule_key. Join on (mule_key, mule_key_epoch); 0 = retired unkeyed construction.
corroboration_count1This submission only (Phase A). Partner-signed counts only — see source tier.
action_requestedMONITORNever ELEVATE_RISK_SCORE while advisory.
urgencyHIGH / CRITICALEnrichment signal (e.g. young domain). Not a freeze instruction.
advisory_noticefixed stringAlso covered by gateway_signature.
source_tierpartner_signed (pilot)First-class trust field — see below.

Do not wire urgency alone to an auto-freeze. Verify gateway_signature over the canonical signed payload (includes disposition / client_reported_confidence / mule_key / mule_key_epoch — contract v2).

STIX egress stamps the same advisory framing on indicator and threat-actor objects (x_waspada_disposition, real mapped confidence — never a fabricated 95). Auto-actionable STIX indicators are suppressed while disposition is advisory (federation rule).

Analyst dispositions

POST /api/v1/alerts/disposition records { alert_id, mule_key, disposition } as a structured disposition_recorded log event (Phase A). There is no queryable store until Phase B.


Source tier & public-triage quarantine (ADV-005)

Status: WhatsApp link-only entry is live (wa.me → on-device sandbox). Anonymous gateway ingest / triage_quarantine writer / magic-link mint are not shipped yet.
Pilot today: only the partner_signed path is bank-affecting. Quarantine table and source_tier enforcement land with public ingest — not after.

Crowd / anonymous reports are a poisoning vector into corroboration and FMS. Most “report a scam” apps let crowd reports drive blocklists directly. Waspada’s pilot posture: public triage cannot reach bank FMS because the ingest door is closed — not because a runtime source_tier / quarantine check refuses it yet. The four checkpoints below are the ship gate before any public ingest opens. That is the honest differentiator for the CISO pack.

Taxonomy (source_tier)

TierOriginTrust
partner_signedPartner RS256 JWT ingestMay increment corroboration, may reach FMS (advisory), may federate via STIX under advisory rules.
public_triageAnonymous sandbox / WhatsApp magic-link funnelQuarantine tier. Never bank-affecting on its own.
analyst_promotedA public_triage indicator an analyst explicitly reviewedAnalyst is the accountable party; only then may the indicator count / federate as if partner-grade (still advisory until corroboration threshold).

mule_key remains the join key across tiers. Promotion happens only via (a) analyst action that sets analyst_promoted, or (b) an independent partner_signed event for the same mule_key.

Hard “never”s — public_triage (named checkpoints)

InvariantEnforcement point
Never increments corroboration.report_countCorroboration UPSERT in persistAlert / alerts-repo — skip when source_tier = public_triage.
Never creates a delivery_outbox row / reaches a bank FMSenqueueDelivery — refuse / no-op for public_triage.
Never counts toward the corroboration_count that gates advisory → actionableresolveAdvisoryContract — only partner_signed (+ post-promotion analyst_promoted) reports contribute.
Never emits an auto-actionable STIX indicator SDOSTIX formatter — suppress actionable indicators while disposition is advisory; public_triage never raises disposition.
Never is written to alertsPublic ingest routes to triage_quarantine, not alerts.

What public_triage may do

  • Extract and show indicators to the submitting user on-device (sandbox).
  • Persist to a separate triage_quarantine table (encrypted envelopes / tenancy pattern aligned with B0 where applicable).
  • Surface on an analyst queue for review.
  • Join later to a partner_signed event on the same mule_key (partner event remains the bank-facing authority).

Quarantine store governance (PDPA)

Anonymous-sourced account numbers are still pseudonymous personal data. triage_quarantine is its own governed surface — not an ungoverned side pool.

ControlRule
Retention30 days default (retention_expires_at) — shorter than the 90-day partner alerts default.
ErasureCrypto-shred / delete row + purge mule_key from quarantine indexes; same backup-bound discipline as partner stores.
DPIAOwn line in DPIA — purpose, legal basis, retention, poisoning residual risk.
RunbookRow in Retention & erasure.

STIX federation

Automated STIX federation suppresses auto-actionable indicator SDOs while disposition is advisory. public_triage origin never produces actionable disposition. Federation of quarantined indicators (if any) must carry x_waspada_source_tier: public_triage and remain non-actionable until promotion + contract rules allow.

CI gate: verifyStixFederationSafety / tests/stix-federation.test.ts — formatter omits patterned indicator SDOs while disposition=advisory; crafted advisory+pattern bundles fail with opaque reason STIX_ADVISORY_PATTERNED_INDICATOR.

Implementation gate

Do not ship WhatsApp magic-link mint, anonymous sandbox→gateway ingest, or a triage_quarantine writer until:

  1. source_tier is present on the ingest/alert path and checked at the four checkpoints above, and
  2. Mint endpoints (if any) are authenticated + rate-limited (never an open link-minting oracle).

Until then, public sandbox stays client-local (draft police report on-device; no anonymous gateway write into alerts / corroboration / outbox).


Abuse resistance & fairness (ADV-006 framing)

last-verified: 615b0d6 (main) · doctrine aligned with ADV-005 + B0 corroboration schema
Audience: CISO / legal — answer to “you don’t collect the victim’s identity, so isn’t this abusable?”

Separate three concerns

#ConcernWaspada stance
1Victim PII (name, NRIC, phone)Deliberately not collected / stripped. Privacy moat — not an authentication control.
2Submitter authentication (who sent the report)Orthogonal to #1. Partner path = RS256 JWT (iss / tenant). Public path = untrusted → quarantine.
3Report verifiability (is the claim true?)Unsolvable from telemetry alone. Every indicator is a signal, not court evidence.

Not collecting victim PII does not mean “accept anonymous unauthenticated bank-affecting reports.” It means: authenticate the channel, quarantine the crowd, keep output advisory.

What already holds

ControlWhat it doesWhere
Partner JWTBounds abuse to an onboarded, contractual, revocable institution — not the open internetIngest auth
ADV-005 quarantinepublic_triage never bumps corroboration, never FMS, never actionable STIX aloneSource tier
Advisory + bank owns freezeNo single report auto-freezesThis contract
Distinct-tenant corroboration schemaPRIMARY KEY (mule_key, reporting_tenant_id) + CHECK (reporting_tenant_id = tenant_id) — one partner cannot appear as N independent reporters for the same muleB0 corroboration table
Pilot gatePhase A always corroboration_count = 1 → disposition stays advisoryresolveAdvisoryContract

Phase C invariant (must enforce when multi-partner corroboration ships): corroboration_count that gates advisory → actionable is COUNT(DISTINCT reporting_tenant_id) for that mule_key, not SUM(report_count). A single partner may increment its own report_count; that must never alone cross CORROBORATION_ACTIONABLE_THRESHOLD.

Residual abuse vectors (open — named for CISO review)

ResidualRiskStatusRequired control
Griefing / noise floodAlert fatigue; repeated flags on a legitimate accountPartial — JWT + rate limits exist; no behavioural anomaly layer yetPer-iss / per-mule_key rate + anomaly detection before promotion / federation
Analyst-promotion social engineeringQuarantine escape hatch launders a fake into partner-grade signalDoctrine onlyDual-review or maker-checker; show provenance (source_tier, original channel); log accountable analyst_id
Unverifiable claimsSigned ≠ honest; no victim testimony or bank ledger in WaspadaBy designPitch as intelligence, not evidence; bank must join to their own txn data
Wrongful flag of alleged muleAccused account holder may be innocent / mistypedInherentAdvisory-only; corroboration; human-in-loop; appeals / reversal (below)

Fairness — the harder PDPA probe

Data we do process about the alleged perpetrator (account / BIC → mule_key) can implicate an innocent third party (dupe mule, OCR error, revenge report). Reporter privacy does not erase that duty. Ethical license to operate:

  1. Advisory disposition + human / bank decision
  2. Distinct-partner corroboration before any stronger disposition
  3. Documented appeals / reversal path for a wrongly flagged account

Appeals / reversal (pilot process — operational, not fully productized)

StepOwnerAction
1. Challenge receivedPartner bank / Waspada opsRecord mule_key, alert_id(s), challenger channel (bank case ID preferred — not victim NRIC in our store)
2. DispositionAnalyst / partnerMark false_positive / dismissed via disposition API or partner case system
3. ContainmentEngineeringSuppress further FMS re-drive for that mule_key under partner policy; purge quarantine rows if public-sourced
4. ErasurePer retention & erasureCrypto-shred alert row + purge mule_key from corroboration / quarantine indexes when legally required

Pilot: process is manual / partner-led. Product automation (self-serve appeal portal) is not claimed.

What we still do not claim

  • That anonymity is “safe” without quarantine
  • That JWT signature proves the underlying scam happened
  • That Waspada can adjudicate innocence of an account holder
  • Automated freeze from public or single-partner reports
  • That extraction / OCR / blinded_hash / client confidence proves a slip is genuine

Evidential integrity — fake slips & false flags (ADV-006b)

Audience: CISO — “What if someone uploads a Photoshopped / GenAI Maybank slip to frame a legitimate account?”

Transcriber ≠ authenticator

The extraction pipeline (OCR / regex / NER) reads pixels. It cannot, by design, tell a genuine bank slip from an edited or AI-generated image. In Malaysia, fake “payment successful” receipts are already a scam tool; convincing fakes are cheap.

Thing people hope will save themWhy it doesn’t
blinded_hash / gateway signatureAuthenticates bytes after extraction, not truth of the claim
Extraction accuracy (P/R on corpus)Measures transcription fidelity, not authenticity
On-device / edge modelAttacker controls device and input
client_reported_confidenceComputed on a hostile device — assertable

A fake slip is the concrete instance of concern #3 above: every slip is an assertion; a fake slip is a false assertion undetectable at extraction.

What actually contains fake slips

Do not promise “we detect Photoshop.” Contain blast radius:

LayerEffect on a fake slip
ADV-005 quarantinePublic / WhatsApp fake → never corroboration / FMS alone
Advisory + bank owns freezeOne fake triggers nothing automated
Distinct-tenant corroborationActionable needs N independent partners (Phase C invariant) — hard to forge across banks
Bank ledger join (FI-side)Ultimate ground truth: “Did this account actually receive that transfer?” — Waspada does not hold the ledger

Targeted false-flag (innocent merchant / personal account on a fabricated “I paid this scammer” slip) is the high-impact version. Contained only by advisory + corroboration + appeals — catastrophic if a bank treats the feed as freeze authority.

Cheap structural validation (edge — filter lazy fakes, not perfect ones)

Shipped today (partial): known Malaysian BIC set; account digits in a broad 8–16 range; DuitNow “no account” treated as correct null.

Roadmap (near-free, on-device): bank-specific account length / format when BIC is known (e.g. Maybank 12-digit expectations); reject or down-score STRUCTURAL_MISMATCH — kills low-effort garbage, not sophisticated fakes.

Forgery / tamper scoring (later — confidence input, never a hard gate)

Template match / metadata / GenAI artifacts against a labeled real-vs-fake MY-slip corpus. Score only; hostile devices can defeat on-device scoring. Corroboration remains the backstop. Do not enter an arms race that bloats the SDK into a “fake detector” product.

Source reputation (roadmap)

Sources whose reports never corroborate get down-weighted; flood patterns on iss / mule_key get throttled — ties to the griefing residual above.

Contract language (say this to banks)

We extract and correlate indicators; we do not — and cannot — prove a slip is genuine. Authenticity is established by independent corroboration and the bank’s own transaction view, never by trusting one image. Waspada output is advisory intelligence, not evidence for automated freeze.

Build vs claim

ItemStatus
Intelligence-not-evidence doctrineClaimed — this page + advisory fields
Quarantine + advisory + distinct-tenant schemaShipped / designed
Bank-specific structural mismatch abortPartial — tighten regex / BIC↔length map
Forgery scoring as confidenceNot built — labeled corpus first
Behavioural source reputationNot built
Productized appeals portalNot built — partner-led process above

Edge tools for citizens. Threat feeds for banks.