Skip to main content

Abuse reports

Aglyn staff only

The queue lives at Staff → Abuse reports (/admin/abuse-reports) and requires a staff claim. The abuseReports collection is allow read: if isStaff() and allow write: if false — every write comes from the Admin SDK, so nothing you do in a Firestore console tab is a supported path.

This is the intake for people who are not our customers. A bank's fraud desk, a browser vendor, a photographer whose work was lifted, a stranger who clicked something wrong — none of them can open a support ticket (that route needs a token and a paid plan), and all of them have somewhere else to go if they cannot reach us. For a phishing site the somewhere else is a domain-level block on *.aglyn.app, which takes every legitimate customer site down with it. That is the failure this queue exists to prevent, and it is the reason an unanswered report is more expensive than it looks.

Where reports come from

One URL, on every origin we serve:

https://<any-site>/api/report-abuse

It works on aglyn.com and on every *.aglyn.app site, because it lives under /api, which the tenant middleware excludes from its per-host rewrite. So a reporter who was looking at dodgy.aglyn.app and a reporter who was looking at our marketing site land on the same form. ?url= pre-fills the reported address.

Things about the form worth knowing before you read a row:

  • No JavaScript, no account, no App Check. GET renders plain HTML, POST takes either a urlencoded form or JSON. The reporters this exists for are often on a locked-down corporate or law-enforcement browser, and an automated phishing feed posts JSON.
  • It does not refuse while a site is suspended. Every other public write on the tenant runtime stops during a lockdown. This one deliberately does not — a suspended site is the most likely subject of a report, and the person who just saw the 503 is the most motivated reporter we will ever get.
  • It does not require the reported site to exist. A mistyped subdomain or an already-deleted site still produces a row. hostId and orgId are resolved when the URL is one of ours and left empty when it is not — so an empty hostId means "we could not resolve it", never "there is nothing here".
  • Rate limit: 5 reports per IP per 10 minutes. A refusal answers 429 and hands the reporter support@aglyn.com rather than a wall. The first report from any source always lands, which matters because a corporate NAT puts a whole fraud department behind one address.
  • A honeypot hit writes nothing. The form carries a hidden website field; a bot that fills it gets a receipt page that looks exactly like success and no document is created. So the queue being empty is weak evidence that nobody reported anything.

Each report gets a reference like AR-3F9A1C2B4D shown to the reporter. It is the first ten characters of the document id, uppercased — so if someone quotes a reference at you, the row is the one whose reference field matches. The reporter's IP is stored nowhere. It goes into the hash that produces the document id and into the rate-limit key, both one-way. The same source reporting the same URL for the same reason merges onto one document and bumps reportCount, so one person cannot make one site look widely reported.

The two timestamps mean different things and both are trustworthy. createdAt is written once, on the first report, and never touched again — so on a row with reportCount above 1 it still answers "when did we first know", which is the question that matters afterwards. updatedAt moves on every repeat.

A repeat also never re-opens a report. Once you have moved a row to actioned or dismissed, the reporter filing again bumps reportCount and leaves your decision where you put it.

Triage by severity

Every category carries a severity. It is not a mood — it says how fast a human has to look.

SeverityCategoriesWhat it means
urgentphishing, csam, malwareLook now.
highdmca, impersonation, illegalSame day.
normalspam, otherWork the queue.

Urgent is urgent because the cost of delay is not paid by us and not paid by our customer. It is paid by whoever clicks the phishing page next, or by the child in the material, or by the visitor whose machine the download takes. That is a different kind of cost from a customer waiting on a billing question, and it does not get cheaper by being ignored overnight.

The second reason, for phishing specifically: an unanswered phishing report is exactly what turns into a domain-level block on *.aglyn.app. The reporter who cannot reach us escalates to a browser vendor or a blocklist, and that block does not distinguish the phishing subdomain from the four hundred honest customer sites beside it. Answering one report quickly is the cheapest insurance we have on the whole platform.

CSAM is not a takedown button

If a report is csam, stop reading the rest of this page and do this:

  1. Do not delete the content. Do not delete the site, do not empty the media, do not "clean up" the workspace, do not delete the report.
  2. Preserve it. Lock the site down (host scope) so the public cannot reach it. Lockdown suppresses; it does not erase. That is the correct instrument here and quarantine is too, for the same reason — a quarantined file still exists and can still be produced.
  3. Escalate to Zach immediately. Whatever hour it is.

The lever is preservation plus notification, not erasure. Deleting the material feels like the responsible act and is close to the opposite of one: it destroys what an investigation needs, and reporting obligations are not discharged by the content going away.

Open item — the reporting mechanics do not exist yet

Who files the report with NCMEC, under whose account, and on what timeline is not established. There is no registered account, no runbook step you can follow, and nothing in the code that does it for you. Until that is settled, the only correct action a staff member can take on a csam report is preserve, suppress, and escalate to Zach — do not improvise a filing, and do not assume someone else has already made one.

The public form no longer implies otherwise (AGL-2045). Its csam hint used to read "Reported to the authorities and handled outside the normal queue", which was half true and dangerous in the false half: a reporter who believes the filing is done may not make one, so the sentence could replace a real CyberTipline report. It now describes only the handling — urgent, out of the normal queue, escalated to the operator — and tells the reporter to file with NCMEC at report.cybertip.org themselves. Assume every reporter has done that and that we have not.

Which lever answers which report

The response tooling is good. Match the size of the lever to the size of the problem — the whole point of having three is that the widest one punishes people who did nothing.

The problem isReach forWhere
One bad file — malware in a PDF, an infringing image, one abusive assetMedia quarantine/admin/media-quarantine
One bad site — a phishing page, a whole site built to deceiveLockdown, host scope/admin/lockdown
A whole workspace acting in bad faith — the same operator rebuilding the same scam across their sitesLockdown, org scope/admin/lockdown

Media quarantine's reason codes already include abuse and dmca, which is deliberate: a report's category maps onto a quarantine reason with no translation step. It is reversible, keyed on the file's content digest, and it does not delete or bill anything — see Asset quarantine for which digest to send and what each key reaches.

Host scope now genuinely freezes the site's client writes. Until AGL-1965, a host-scope suspension stopped the public site and every Admin-SDK route and did not stop the browser's direct Firestore writes — so an editor with a live session could keep editing a phishing site staff had just suspended, and republish it. The Firestore rules now carry a hostSuspended arm, so a suspended site cannot be republished.

That is only true once the rules are deployed

cloud/firebase-firestore.rules deploys separately from the app. An app deploy that carries the rules file in the repo has not applied it. If you are relying on a host-scope lock to stop republishing — and on a phishing takedown you are — confirm the rules in force are the ones with the host arm, rather than assuming the last deploy included them.

Two known edges of a host-scope lock, both carried in AGL-1981, both worth knowing before you promise a customer or yourself that a site is frozen:

  • A timed suspension never expires in the rules. The server-side helpers honour suspendedUntilMs and the rules do not. So when a timed lock lapses the site starts serving again while the client SDK stays frozen — the customer gets their site back and cannot edit it, with no error explaining why. Prefer an untimed lock you come back and lift by hand.
  • Org-level data is not frozen by a host-scope lock. A host suspended in read-only mode keeps serving, and the pages it serves render org-level datasets and media that the host arm does not reach. If the offending content is org-level rather than site-level, host scope is the wrong scope.

Whatever you pull, the lockdown and quarantine surfaces both read the state back after they write it and say NOT CONFIRMED when the re-read disagrees. Believe the re-read, not the click.

Statuses

StatusWhat it means
openNobody has looked at it. Every report starts here.
reviewingYou are working it right now. Set it so a second person does not duplicate the investigation — and so an urgent row that has been reviewing for hours is visibly stuck rather than invisibly stuck.
actionedWe did something: a quarantine, a lockdown, a scope escalation. Say what, in the row.
dismissedWe looked and are doing nothing. A dismissal is a decision and needs a reason — "not our host", "the page is what it claims to be", "duplicate of AR-…".

dismissed is not the same as unread. If you dismiss without a reason, the next person to receive a report about the same site has no idea whether we already considered it.

What we do not tell people

Two rules, both narrow and both firm.

We do not tell the reporter what we decided about a specific site. Not "we suspended them", not "we found nothing". A reporter has standing to know their report arrived — that is the reference number — and no standing to learn what enforcement exists against a named customer. The receipt page says this plainly, so a reporter who expected a verdict was told up front they would not get one.

We do not pass reporter details to the site owner. One exception, and it is required rather than optional: on a DMCA notice the site owner needs the notice, including who sent it, because their right to counter-notice is meaningless without knowing what and who they are answering. That is why a copyright notice cannot be anonymous and every other category can be.

The DMCA path

A valid takedown notice under 17 U.S.C. §512(c)(3) carries, among other things:

  1. Identification of the copyrighted work said to be infringed.
  2. A good-faith statement that the use is not authorised by the owner, its agent, or the law.
  3. A statement under penalty of perjury that the information is accurate and the sender is authorised to act for the owner.
  4. A physical or electronic signature.

The form enforces all four — plus a reply address, which the other categories do not require — and refuses a dmca submission missing any of them. So a report in the queue with category: dmca has the affirmations on it or it would not be there. Read them anyway: enforcing that a field is non-empty is not the same as the field saying something.

We do not adjudicate the claim. Nothing here decides whether the copyright claim is good. We record what was asserted, by whom, at what time. If the notice is facially complete and points at content we host, the proportionate response is usually quarantining the specific asset rather than locking the site.

The site owner has a right to counter-notice. That path now exists — see Counter-notices below.

The agent is filed. The publication half is not.

Registered. Copyright Office designated-agent record DMCA-1038349, active since 2026-08-18, next renewal 2029-08-18. Agent: Copyright Compliance Department, dmca@aglyn.com, 512-222-8232, c/o Northwest Registered Agent, LLC., 5900 Balcones Drive STE 100, Austin, TX 78731. Earlier text on this page and on AGL-1618 said no filing existed; that was corrected 2026-08-19.

What is still outstanding is publication, and it is a real condition, not paperwork. §512(c)(2) requires those same four details — name, address, phone, email — to be available to the public through the service, and §512(i)(1)(A) conditions the entire safe harbour on having informed subscribers of the repeat-infringer policy. Neither is on /legal/dmca yet (AGL-2035, AGL-2007), and /legal/dmca is besigner-published content, so closing them is a publication pass rather than a repo edit.

Two things follow for you. Details on the filing and details on the page must match — if you are ever asked to correct one, correct both. And until the publication lands, still do not tell a reporter or a customer that we are operating inside the safe harbour: the registration is necessary and is not sufficient.

Counter-notices — the put-back

A subscriber whose material we removed can answer with a counter-notice under §512(g). This is the process that protects us from the other side: following the put-back procedure is what shields us from a claim by our own customer for taking their site down on a stranger's say-so.

Where they file. https://{any-host}/api/counter-notice — public, no login, no JavaScript. It has to be reachable without an account on purpose: a host-scope takedown 503s the site and freezes every client write, and an org-scope one keeps the customer out of the console entirely. A counter-notice form behind a sign-in is unreachable in exactly the circumstances it exists for.

What the form collects, all of it required, because a counter-notice missing any element is not a weaker document — it is one with no legal effect:

  1. Identification of the material and where it appeared before removal.
  2. A statement under penalty of perjury that it was removed as a result of mistake or misidentification.
  3. Consent to the jurisdiction of the Federal District Court for the subscriber's address — or, if they are outside the US, any district in which Aglyn may be found.
  4. Agreement to accept service of process from the complainant.
  5. Name, postal address, telephone number and an electronic signature.

The clock, and why it is not yours to move

§512(g)(2) gives a sequence with a deadline in it:

  • Promptly send the complainant a copy and tell them the material goes back.
  • Put it back not less than 10 and not more than 14 business days after receipt of the counter-notice — unless the complainant first tells us they have filed a court action seeking to restrain the subscriber.

Two things follow, and both are built rather than described:

  • The clock counts from when the subscriber pressed the button, not from when you opened the queue. Time we take to process a counter-notice comes out of the remaining window; it is never added to the customer's lockout. A counter-notice forwarded eleven days late schedules the same restore date as one forwarded the same hour.
  • Forwarding schedules the reversal. Moving a counter-notice to forwarded stamps the site's own suspension expiry (suspendedUntilMs) with the restore instant, so the lock lifts itself on the statutory date. There is no cron job to fail quietly and nobody to remember.

The queue shows the earliest and latest lawful instants either side of the date we picked, so you can see it sits inside the window rather than take our word.

The steps

StepWhat it meansWhat it does to the site
receivedFiled by the subscriber. Nothing sent yet.Nothing. The deadline is already running.
forwardedCopy sent to the complainant — the §512(g)(2)(A) obligation.Stamps the restore date onto the suspension.
restoredAccess is back.Withdraws the strike the original notice earned.
suitFiledThe complainant told us they filed a court action.Cancels the scheduled restoration; the material stays down.
withdrawnThe subscriber took it back.Cancels the scheduled restoration.
rejectedNot a counter-notice at all — a misfiled question.Cancels the scheduled restoration.

rejected is not a ruling on the merits, and must never be used as one. We do not adjudicate a counter-notice any more than we adjudicate a notice.

Two things forwarding will not do, deliberately:

  • It never creates a suspension. If the site is not currently suspended, nothing is written and the confirmation says so. Read that message: it means no put-back was scheduled, because there was no lock to schedule the end of.
  • It never extends one. If the suspension already ends sooner than the statutory date, the sooner date stands. A subscriber asking for their site back must not be able to keep it down longer than staff imposed.

An overdue restoration is a breach, and the queue puts it at the top in red. Restoring late is its own §512(g) failure, and unlike most things on this page it is a harm we are causing to our own customer.

Repeat infringers

§512(i) conditions the entire safe harbour — every limitation in §512, not just the hosting one — on having adopted and reasonably implemented a policy for terminating repeat infringers, and on informing subscribers of it. Providers most often lose on the second half: a policy that exists as prose while nothing counts anything is what courts have declined to credit.

What counts as a strike. One upheld copyright notice: a report with category: dmca that you moved to actioned. Not a received notice — anyone can send one, and counting receipts would let a competitor close a customer's account with three emails. Not a phishing or malware takedown either; §512(i) is about infringement.

Counted against the workspace, not the site. Someone who loses one site and opens another in the same workspace has not been terminated in any sense the statute would recognise.

Strikes come off. A strike is withdrawn when:

  • you move the report back off actioned (staffReversed);
  • a counter-notice runs its course and access is restored (counterNoticeRestored) — the process reversed the takedown, so a strike surviving it would count an infringement we just declined to affirm;
  • the complainant retracts the notice (noticeWithdrawn).

Withdrawal marks the ledger row rather than deleting it, so the history stays answerable.

The threshold

StrikesLevelWhat happens
1WarnedChip on the report row. Tell the customer.
2Final warningOne more reaches the threshold.
3+Termination thresholdThe queue refuses to close any further copyright report on that account until you record a decision.

That refusal is the point. It is not an automatic termination — closing a paying customer's account on three assertions by strangers, with no human in the loop, is nothing §512 asks for. The statute says "in appropriate circumstances", and judging the circumstances is the part a person does.

"Not this time" is a valid answer, and it is recorded in the audit log exactly like a termination. If two of the three strikes are the same complainant over the same disputed licence, say so and escalate — that is the policy being reasonably implemented.

A strike count shown as blank means unknown, not zero: the queue looks up a bounded number of accounts per page. Check the account directly before closing a report on one of them.

Known gaps

Honest list. Every one of these is a thing you will otherwise discover during an incident.

  • Only the urgent categories are pushed at you. A first report in phishing, csam or malware fans out through notifyStaff and appears in the console notifications menu for every staff-claim holder. Everything else — dmca, impersonation, illegal, spam, other — waits in the queue, and opening the queue is the only way anyone learns about those. That is a deliberate trade, not an oversight: notifying on every report would make the notification unread, and the one it would cost us is the phishing one. It does mean a copyright notice can sit for as long as nobody looks, so looking is a habit somebody has to keep.
  • The notification fires once per report, not per submission. A reporter resubmitting cannot re-alert you, which is right — and also means a situation getting worse does not raise its voice.
  • No auto-acknowledgement email to the reporter. They get the receipt page with their reference at submit time and nothing afterwards. If they close the tab before reading it, they have no record they reported anything.
  • abuse@aglyn.com and dmca@aglyn.com deliver — confirmed 2026-08-19 (AGL-1911) by reading Google Workspace group configuration, not by a test send. Both are Google Groups ("Legal - Abuse", "Legal - DMCA") with Who can post = Anyone on the web, no moderation, and one member, zach@aglyn.com, subscribed Each email. A test send could not have established this: AGL-1577's default routing accepts mail for non-existent @aglyn.com addresses too and suppresses the bounce, so "it didn't bounce" is equally true of an address that was never created. The check that can fail is groups.google.com/a/aglyn.com/g/<name> — it returns 404 for an address that does not exist. Each is a single-member group with no auto-acknowledgement, so a report sits unread and unacknowledged whenever Zach is away (AGL-2400). The form is still the better route — it captures the §512(c)(3)(A) fields a free-text email will not. If someone asks where to send a report, send them to /api/report-abuse; the address printed on the form is support@aglyn.com.
  • No NCMEC mechanics. See CSAM — preserve, suppress, escalate. Still true, and the public form now says so by omission rather than claiming the opposite (AGL-2045). Building a real reporting path — registered CyberTipline account, the §2258A duties that attach with it, and retention — is a decision for Zach, not something to improvise on a live report.
  • The designated agent is filed; the required publication is not. The registration is live (DMCA-1038349, active 2026-08-18). What is missing is the §512(c)(2) publication of the agent's four details and the §512(i) repeat-infringer policy text on /legal/dmca — AGL-2035 and AGL-2007, both a besigner publication pass. See the DMCA path.
  • Forwarding a counter-notice is a manual send. The queue records that you forwarded it and schedules the put-back; it does not email the complainant for you. You send the copy — including the subscriber's name, address and phone, which §512(g)(2)(A) requires us to pass on — and then mark the step. Marking forwarded without actually sending it schedules a restoration while leaving the complainant unaware, which is the one sequence here that harms the party who did nothing wrong.
  • A counter-notice with no notice reference cannot withdraw its strike. The subscriber is not required to quote one and the form does not insist. When it is missing, restoring leaves the strike standing — match it up and reverse the report by hand.
  • Nothing warns the customer that they are on a strike. The count is staff- side. §512(i) requires subscribers to be informed of the policy, which the published policy does; telling this customer about this strike is still a message a person sends.