This repository has been archived by the owner on Feb 8, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
File first draft of first incident report (#329)
- Loading branch information
1 parent
ecc524e
commit a99f4c1
Showing
3 changed files
with
400 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,386 @@ | ||
import locale | ||
import math | ||
from collections import defaultdict | ||
from decimal import Decimal | ||
|
||
from aspen import Response | ||
from gittip import db, CARDINALS | ||
|
||
^L | ||
_suspicious = db.fetchall(""" | ||
|
||
SELECT id | ||
, balance | ||
, ctime | ||
FROM participants | ||
WHERE is_suspicious | ||
AND ctime < '2012-11-05'::timestamptz | ||
ORDER BY ctime | ||
|
||
""") | ||
if _suspicious is None: | ||
_suspicious = [] | ||
|
||
|
||
def suspicious(): | ||
for person in _suspicious: | ||
person['gives_to'] = db.fetchall(""" | ||
|
||
SELECT DISTINCT tippee AS id | ||
, is_suspicious | ||
, tips.ctime | ||
FROM tips | ||
JOIN participants p | ||
ON tippee = p.id | ||
WHERE tipper=%s | ||
AND tips.ctime < '2012-11-05'::timestamptz | ||
ORDER BY tips.ctime | ||
|
||
|
||
""", (person['id'],)) | ||
person['receives_from'] = db.fetchall(""" | ||
|
||
SELECT DISTINCT tipper AS id | ||
, is_suspicious | ||
, tips.ctime | ||
FROM tips | ||
JOIN participants p | ||
ON tipper = p.id | ||
WHERE tippee=%s | ||
AND tips.ctime < '2012-11-05'::timestamptz | ||
ORDER BY tips.ctime | ||
|
||
""", (person['id'],)) | ||
|
||
person['transfers'] = list(db.fetchall(""" | ||
|
||
SELECT tippee | ||
, tipper | ||
, amount | ||
, timestamp | ||
FROM transfers | ||
WHERE tipper=%s OR tippee=%s | ||
AND timestamp < '2012-11-05'::timestamptz | ||
ORDER BY timestamp | ||
|
||
|
||
""", (person['id'], person['id']))) | ||
|
||
person['exchanges'] = list(db.fetchall(""" | ||
|
||
SELECT amount, fee, timestamp | ||
FROM exchanges | ||
WHERE participant_id=%s | ||
AND timestamp < '2012-11-05'::timestamptz | ||
ORDER BY timestamp | ||
|
||
|
||
""", (person['id'],))) | ||
|
||
yield person | ||
suspicious = list(suspicious()) | ||
names = set([person['id'] for person in suspicious]) | ||
|
||
total_charged = 0 | ||
total_withdrawn = 0 | ||
gittip_fees = 0 | ||
balanced_fees = 0 | ||
total_escrowed = 0 | ||
total_legit = 0 | ||
total_fraudulent = 0 | ||
|
||
bystanders = defaultdict(lambda: [Decimal('0.00')]) | ||
|
||
earliest = None | ||
latest = None | ||
|
||
for person in suspicious: | ||
total_escrowed += person['balance'] | ||
for transfer in person['transfers']: | ||
if transfer['tippee'] not in names: | ||
total_legit += transfer['amount'] | ||
bystanders[transfer['tippee']][0] += transfer['amount'] | ||
for exchange in person['exchanges']: | ||
total_fraudulent += 1 | ||
amount, fee = exchange['amount'], exchange['fee'] | ||
if amount > 0: | ||
if earliest is None or earliest > exchange['timestamp']: | ||
earliest = exchange['timestamp'] | ||
if latest is None or latest < exchange['timestamp']: | ||
latest = exchange['timestamp'] | ||
amount_charged = exchange['amount'] + fee | ||
total_charged += amount_charged | ||
balanced_fee = (amount_charged * Decimal('0.029')) + Decimal('0.30') | ||
balanced_fee = Decimal(balanced_fee) | ||
else: | ||
total_withdrawn += abs(exchange['amount']) | ||
balanced_fee = Decimal('0.25') | ||
balanced_fees += balanced_fee | ||
gittip_fees += fee - balanced_fee | ||
assert total_legit == total_charged - total_withdrawn - gittip_fees - balanced_fees - total_escrowed | ||
|
||
nweeks = CARDINALS[((latest.date() - earliest.date()).days / 7) + 1] | ||
|
||
overall = db.fetchone(""" | ||
|
||
SELECT sum(amount + fee), count(amount) | ||
FROM exchanges | ||
WHERE amount > 0 | ||
AND timestamp > %s | ||
AND timestamp < '2012-11-01'::timestamptz | ||
|
||
""", (earliest.date(),)) | ||
overall_volume = overall['sum'] | ||
overall_transactions = overall['count'] | ||
percentage_stolen = (total_charged / overall_volume) * 100 | ||
percentage_fraudulent = (total_fraudulent / float(overall_transactions)) * 100 | ||
|
||
|
||
pad = lambda d, n: locale.format("%.2f", d, grouping=True).rjust(n).replace(' ', ' ') | ||
|
||
w = 9 | ||
total_charged = pad(total_charged, w) | ||
total_withdrawn = pad(total_withdrawn, w) | ||
gittip_fees = pad(gittip_fees, w) | ||
balanced_fees = pad(balanced_fees, w) | ||
total_escrowed = pad(total_escrowed, w) | ||
total_legit = pad(total_legit, w) | ||
overall_volume = pad(overall_volume, w) | ||
percentage_stolen = pad(percentage_stolen, w) | ||
|
||
overall_transactions = pad(overall_transactions, w)[:-3] + (' ' * 3) | ||
total_fraudulent = pad(total_fraudulent, w)[:-3] + (' ' * 3) | ||
percentage_fraudulent = pad(percentage_fraudulent, w) | ||
|
||
|
||
bystander_balances = db.fetchall(""" | ||
|
||
SELECT id, balance FROM participants WHERE id = ANY(%s) | ||
|
||
""", (list(bystanders),)) | ||
if bystander_balances is None: | ||
bystander_balances = [] | ||
for row in bystander_balances: | ||
bystanders[row['id']].append(row['balance']) | ||
|
||
bystanders = sorted(bystanders.items(), key=lambda i: i[1], reverse=True) | ||
|
||
fmtdate = lambda d: str(d)[:10] | ||
|
||
^L | ||
{% extends templates/base.html %} | ||
{% block body %} | ||
<style> | ||
TH { | ||
font-weight: normal; | ||
text-align: center; | ||
padding: 0; | ||
} | ||
TD { | ||
text-align: left; | ||
vertical-align: top; | ||
} | ||
.summary TH { | ||
text-align: left; | ||
padding-right: 12pt; | ||
} | ||
.summary B { | ||
display: block; | ||
padding-bottom: 12pt; | ||
} | ||
.details TH { vertical-align: bottom; } | ||
.details TD { vertical-align: top; } | ||
.details TD, .details TH { | ||
text-align: left; | ||
border-bottom: 1px solid #B2A196; | ||
white-space: nowrap; | ||
padding: 0pt; | ||
font-size: 10pt; | ||
line-height: 13pt; | ||
} | ||
.balance { | ||
} | ||
.amount { | ||
text-align: center; | ||
padding-right: 6pt; | ||
font-family: Lucida Mono, monospace; | ||
} | ||
.details .tips, | ||
.details .transfers, | ||
.details .exchanges { | ||
padding-left: 12pt; | ||
} | ||
.date { | ||
font-family: Lucida Mono, monospace; | ||
font-size: smaller; | ||
color: #999; | ||
float: right; | ||
} | ||
.no-float { | ||
float: none; | ||
} | ||
.suspicious { | ||
font-weight: bold; | ||
color: red; | ||
} | ||
.charge { | ||
color: red; | ||
} | ||
</style> | ||
|
||
<div id="their-voice"> | ||
|
||
<h2 class="first">The Delpan Incident</h2> | ||
|
||
<p>Gittip’s first <a href="./">fraud incident</a> surfaced when an | ||
unknown user named delpan appeared among the top receivers on the site. | ||
This page provides data and collects resources related to the incident.</p> | ||
|
||
|
||
<h3>Status</h3> | ||
|
||
<p>This incident is <b>open</b>.</p> | ||
|
||
|
||
<h3>Related Resources</h3> | ||
|
||
<ul> | ||
|
||
<li>GitHub ticket: “<a | ||
href="https://github.com/whit537/www.gittip.com/issues/329">make sure | ||
"delpan" is not stealing money :-)</a>”</li> | ||
|
||
<li>Blog post: “<a | ||
href="http://blog.gittip.com/post/35057426257/stolen-money-on-gittip-part-1">Stolen | ||
Money on Gittip, Part 1</a>”</li> | ||
|
||
<li>Hacker News discussion: “<a | ||
href="http://news.ycombinator.com/item?id=4743954">Stolen Money on | ||
Gittip, Part 1</a>”</li> | ||
|
||
<li>Vice Motherboard: “<a | ||
href="http://motherboard.vice.com/2012/11/6/thieves-launder-money-by-crowdfunding-themselves--2">Thieves | ||
Launder Money by Crowdfunding Themselves</a>”</li> | ||
|
||
<li>Secure Computing Magazine: “<a | ||
href="http://www.scmagazine.com.au/News/322118,fraudsters-launder-cash-though-grants-startup.aspx?utm_source=feed&utm_medium=rss&utm_campaign=SC+Magazine+All+Articles+feed">Fraudsters | ||
launder cash though grants startup</a>”</li> | ||
|
||
</ul> | ||
|
||
</div> | ||
|
||
|
||
<h3>Summary</h3> | ||
|
||
<p>The fraudulent credit card transactions associated with this incident | ||
occured over a period of <b>{{ nweeks }} weeks</b>, from | ||
{{ earliest.strftime("%B %d") }} to {{ latest.strftime("%B %d, %Y") }}.</p> | ||
|
||
<table class="summary"> | ||
<tr><th>Withdrawn to a Bank Account</th> | ||
<td class="amount">${{ total_withdrawn }} </td></tr> | ||
<tr><th>Given to Innocent Bystanders</th> | ||
<td class="amount">${{ total_legit }} </td></tr> | ||
<tr><th>Still Escrowed in Gittip</th> | ||
<td class="amount">${{ total_escrowed }} </td></tr> | ||
<tr><th>Fees - Balanced</th> | ||
<td class="amount">${{ balanced_fees }} </td></tr> | ||
<tr><th>Fees - Gittip</th> | ||
<td class="amount">${{ gittip_fees }} </td></tr> | ||
<tr><th><b>Total Stolen</b></th> | ||
<td class="amount"><b>${{ total_charged }} </b></td></tr> | ||
<tr><th>Overall Volume</th> | ||
<td class="amount">${{ overall_volume }} </td></tr> | ||
<tr><th><b>Percentage Stolen</b></th> | ||
<td class="amount"><b> {{ percentage_stolen }}%</b></td></tr> | ||
<tr><th>Total Credit Card Transactions</b></th> | ||
<td class="amount"> {{ overall_transactions }} </td></tr> | ||
<tr><th>Fraudulent Transactions</th> | ||
<td class="amount"> {{ total_fraudulent }} </td></tr> | ||
<tr><th><b>Percentage Fraudulent</b></th> | ||
<td class="amount"><b> {{ percentage_fraudulent }}%</b></td></tr> | ||
</table> | ||
|
||
|
||
<h3>Innocent Bystanders (N = {{ len(bystanders) }})</h3> | ||
|
||
<p>These people inadvertently received stolen money as part of this | ||
incident.</p> | ||
|
||
<table class="summary"> | ||
<tr><th></th><th>Received</th>{% if user.ADMIN %}<th>Balance</th>{% end %}</tr> | ||
{% for name, (amount, balance) in bystanders %} | ||
<tr> | ||
<th><a href="/{{ name }}/">{{ name }}</a></th> | ||
<td class="amount">${{ pad(amount, 9) }}</td> | ||
{% if user.ADMIN %}<td class="amount">${{ pad(balance, 7) }}</td>{% end %} | ||
</tr> | ||
{% end %} | ||
<tr> | ||
<td><b>Total</b></td> | ||
<td class="amount"><b>${{ total_legit }}</b></td> | ||
{% if user.ADMIN %}<td></td>{% end %} | ||
</tr> | ||
</table> | ||
|
||
|
||
<h3>Suspicious Accounts (N = {{ len(suspicious) }})</h3> | ||
|
||
<table class="details"> | ||
<tr> | ||
<th></th> | ||
<th class="balance">Balance</th> | ||
<th class="tips">Tip Graph</th> | ||
<th class="transfers">Transfers</th> | ||
<th class="exchanges">Exchanges & Fees<br /> | ||
<span class="help">Negative means bank deposit,<br />positive means credit card charge</span></th> | ||
</tr> | ||
{% for row in suspicious %} | ||
<tr> | ||
<td><a href="/{{ row['id'] }}/">{{ row['id'] }}</a><br /> | ||
<span class="date no-float">{{ fmtdate(row['ctime']) }}</span></td> | ||
<td class="balance amount">{{ pad(row['balance'], 5) }}</td> | ||
<td class="tips"> | ||
{% for person in row['gives_to'] %} | ||
→<a href="/{{ person['id'] }}/"{% if person['is_suspicious'] %} class="suspicious"{% end %}>{{ person['id'] }}</a><br /> | ||
{% end %} | ||
{% for person in row['receives_from'] %} | ||
←<a href="/{{ person['id'] }}/"{% if person['is_suspicious'] %} class="suspicious"{% end %}>{{ person['id'] }}</a><br /> | ||
{% end %} | ||
</td> | ||
<td class="transfers"> | ||
{% for transfer in row['transfers'] %} | ||
{% if row['id'] == transfer['tipper'] %} | ||
<span class="amount">{{ pad(transfer['amount'], 5)}}</span>→ | ||
<a href="/{{ transfer['tippee'] }}/"{% if transfer['tippee'] in names %} | ||
class="suspicious"{% end %}>{{ transfer['tippee'] }}</a> | ||
{% else %} | ||
<span class="amount">{{ pad(transfer['amount'], 5) }}</span>← | ||
<a href="/{{ transfer['tipper'] }}/"{% if transfer['tipper'] in names %} | ||
class="suspicious"{% end %}>{{ transfer['tipper'] }}</a> | ||
{% end %} | ||
<span class="date"> | ||
{{ str(transfer['timestamp'])[:10] }} | ||
</span> | ||
<br /> | ||
{% end %} | ||
</td> | ||
<td class="exchanges"> | ||
{% for exchange in row['exchanges'] %} | ||
<span class="amount{% if exchange['amount'] < 0 %} charge{% end %}"> | ||
{{ pad(exchange['amount'], 6) }} | ||
{{ pad(exchange['fee'], 4) }} | ||
</span> | ||
<span class="date"> | ||
{{ str(exchange['timestamp'])[:10] }} | ||
</span> | ||
<br /> | ||
{% end %} | ||
</td> | ||
</tr> | ||
{% end %} | ||
</table> | ||
<br /> | ||
<hr /> | ||
{% end %} |
Oops, something went wrong.