When a moderation check only asks whether content is banned, a new upload with no decision yet passes the check and gets served. The fix is to ask for an affirmative approved state and deny everything else.
This is a failure pattern to test for, not a diagnosis of any particular codebase. The exact cause depends on your schema, defaults, query logic, and delivery path, so the steps below show what to inspect and how to confirm it.
Why “not banned” quietly becomes “allowed”
The risky model is a single boolean such as banned. The publish logic then reads something like this:
// Risky: a missing or pending record passes
if (content.banned !== true) {
publish(content);
}
A brand-new record has no moderation decision yet, so banned is either null, absent, or false by default. In every one of those cases, banned !== true evaluates to true. The check answers “has anyone rejected this?” when it should answer “has anyone approved this?”
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
Several related conditions produce the same effective result:
- A nullable or defaulted database column that is written as false before moderation has run.
- A query that returns no row, where the code treats “no record” as “no restriction.”
- An unrecognized state string that falls through to the publish branch.
- A stale cached authorization decision made before the upload existed or before a revocation.
- A moderation call that failed, after which the code continued as if the content were clean.
Each of these is a candidate, not a conclusion. Confirm which one applies by inspecting the actual schema and the publish code path.
Model moderation as explicit states
Cloudinary’s Node.js SDK moderation guidance puts the principle in one line: “Model moderation as a state machine, not a boolean.” The quote comes from the SDK documentation itself, which does not name an individual author. Its guidance on upload moderation is at Cloudinary: Moderate an upload.
A workable set of states for most applications is shown below. The “Delivered?” column is the rule that matters: only one state grants access.
Rank #2
| State | Meaning | Delivered? |
|---|---|---|
pending |
Uploaded; no verdict has been committed | No |
approved |
An affirmative verdict has been committed | Yes |
rejected |
A verdict that the content must not be shown | No |
revoked |
Previously approved, now withdrawn | No |
Define the allowed transitions explicitly so that a bug cannot skip a step. A conservative set is:
pendingtoapprovedorrejected, written by the moderation outcome handler.approvedtorevoked, written by a takedown or re-review action.revokedorrejectedback toapprovedonly through a new, recorded review, never by a timer or a retry.- Any other transition, and any unknown state, is rejected and logged.
Gate the delivery path, not only the upload handler
A check in the upload controller is not enough. Content can reach the public through several paths, and each one needs the gate:
- Public object promotion: moving an object from a private location to a public one.
- Serving boundaries: any route, signed URL generator, or proxy that returns bytes.
- Caches and CDN keys: an entry cached while the content was pending can outlive its state.
- Generated variants: thumbnails and resized copies must follow their source’s state.
- Warmup jobs: pre-fetching scripts can populate caches before approval.
Keep uploads under private, non-delivery identifiers while review is pending. Do not derive a public URL from the uploaded filename, and publish or promote an object only after the approval is committed. Cloudinary’s Node SDK guidance warns that pending assets are deliverable by default unless application code gates delivery, so the gate belongs in your code even when a vendor supplies a status field.
A fail-closed check looks like this:
async function canDeliver(contentId) {
let record;
try {
record = await store.getModerationState(contentId);
} catch (err) {
return false; // a failed lookup denies access
}
return Boolean(record) && record.state === 'approved';
}
The same function should be called at promotion time and at serving time. Calling it only once, at upload, leaves revocations and cache entries unchecked.
Rank #3
Asynchronous moderation: pending is not a verdict
Asynchronous moderation adds a window in which the application holds content without a decision. Treat that window as unresolved until a valid final result arrives.
Stream’s Node moderation documentation describes a synchronous result and an optional stateful asynchronous flow. With async_response: true, the initial result is pending, and final results arrive through completion webhooks. The same documentation advises against using this mode without entity fields. Read the details at Stream: Content moderation for Node.
Two rules follow from this flow:
- The
pendingacknowledgement must never move content to a deliverable state. - Only a completed, valid result may change the state, and the handler must keep the content unavailable until that result is processed.
Missing actions and failed analysis
Stream documents per-field actions of keep, flag, or remove. The same guide says an action may be omitted when an error is present, and it explicitly says never to treat a missing action as keep. It also states that failed analysis means the listed content IDs were not screened.
In practice, a missing action or an error response should lead to one of two outcomes: retry the analysis, or move the affected fields into a quarantine state that is preserved for review. Neither outcome should promote the content to approved.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Rank #4
Webhooks, retries, and stuck items
- Make the completion handler idempotent, so that a repeated delivery of the same result does not change the state twice.
- Store the verdict with the content ID and the source of the decision before applying the transition.
- Alert on items that remain
pendingbeyond a threshold you define for your own workload. Stuck items are a symptom of a dropped webhook or a failed worker, and a timeout must not be treated as approval.
Use the review queue to separate “waiting” from “acted on”
Stream’s review queue documentation describes retrieval with filters for entity, reviewed state, moderation category, and recommended action. It also supports pagination and item locks that reduce duplicate moderator work. Details are at Stream: Review Queue.
These features help answer a specific debugging question: was an item still awaiting review when it became visible, or did several workers or moderators act on it at about the same time? Filtering by reviewed state for a given entity shows whether the item had a decision when it was served. Lock behavior shows whether two actions raced.
Debugging sequence
Work through these steps in order. Each one narrows the search before the next.
- Inspect defaults and the empty case. Check the schema for the moderation column. Confirm whether a new record is ever null or absent, and whether any query treats a missing row as permission.
- Trace the decision and the transition. Find the code that writes the verdict. Confirm which states it can write, and whether it can write an unknown value.
- Check the publication worker. Confirm that it reads the committed state, not a value from an earlier message, and that a failed lookup returns a denial.
- Inspect every delivery path. Review object URLs, CDN and cache keys, thumbnails, and warmup jobs for any path that serves bytes without calling the gate.
- Test revocation as well as first publication. Approve an item, serve it, revoke it, and confirm that the revoked item stops being served through every path, including caches.
Trace one identifier through the lifecycle
Follow a single opaque content ID from upload acceptance through moderation persistence, queue or worker processing, promotion, and cache fill. Log every denied promotion and delivery attempt so that the first accidental allow can be found. Each denial record should include:
Free tools Windows power users keep installed
One-click scans. No signup required.
- The opaque content or asset ID.
- The observed state at the time of the check, or “missing” if no record existed.
- The caller or job ID that made the attempt.
- The destination class, such as public promotion, signed URL, or cache fill.
Do not write customer content, filenames, or personal data into operational logs. The identifier is enough to trace the path.
Options and trade-offs
| Approach | Advantages | Risks and what to compare |
|---|---|---|
| Durable approval check at delivery | The current persisted state is consulted at the access boundary, so a revocation takes effect without waiting for a cached decision. | More read load and latency. The check itself must fail closed when the store is unavailable. |
| Cached approval decision | Reduces repeated durable reads for high-volume delivery. | Creates a revocation window. Invalidation must reach authorization entries and every derived variant. Use it only when the window is bounded and observable. |
| Private quarantine, then approved promotion | Keeps the pre-approval object off public delivery paths. | Requires careful promotion, retry, cleanup, and cache handling. Do not derive public URLs from upload filenames. |
| Vendor-managed media moderation | Can supply a review queue and status metadata. | Cloudinary’s Node SDK documentation says pending assets are deliverable by default unless the application gates them, so the application still needs to understand delivery behavior. Compare each vendor’s state model, webhook behavior, and operational control. |
Hosted services such as Stream and Cloudinary can handle classification, queues, and status tracking. They do not replace the application-side gate. Whatever the vendor reports, your delivery code must still decide who sees the bytes.
The bottom line below covers the single rule to take into your next review.
The Bottom Line
Treat a missing decision as pending, and let only an affirmative approved state make content public. Check that state at promotion and at every serving boundary, including caches and generated variants, and keep denials on a failed lookup, an unknown state, or an absent action.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




