Block Malicious URL Entry
DevOps & Securityblock-malicious-url-entryBlock Malicious URL Entry is an Agent Skill for devops & security published in the zws-im/zws repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.
Safely block malicious zws short URLs and redirected domains in production PostgreSQL and Redis. Use when asked to block a zws.im URL, malicious target URL, hostname, or redirected domain.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/zws-im/zws/blob/HEAD/.factory/skills/block-malicious-url-entry/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/block-malicious-url-entry, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
Block Malicious URL Entry
Use this skill when a user asks to block a malicious zws.im short URL, its redirected destination, or a target hostname/domain.
This is a production-adjacent workflow. Treat PostgreSQL and Redis writes as high risk, keep secrets out of output, and verify with public/API requests before reporting completion.
Required inputs
- Suspicious
zws.imURL and/or target URL. - Target hostname/domain when the user already knows it.
- Production environment scope, normally Railway
productionand serviceAPI.
Safety rules
- Never print
DATABASE_URL,REDIS_URL, API keys, passwords, or full environment output. - Prefer Railway CLI
railway run --service API --environment production -- ...over local.envfor production data. - Always run a dry run before applying changes unless the user explicitly instructs an immediate production block.
- Use parameterized SQL and transactions. Never use broad unscoped
UPDATEor anyDELETE. - Update only:
blocked_hostnames.hostnameurls.blocked- Redis set
blocked-hostnames
- If the target domain is ambiguous, stop and clarify before applying.
Workflow
-
Inspect the relevant code paths if needed:
apps/api/src/db/schema.tsapps/api/src/blocked-hostnames/blocked-hostnames.service.tsapps/api/src/blocked-urls/blocked-urls.service.tsapps/api/src/urls/urls.service.tsapps/api/src/urls/dtos/short.dto.tsapps/web/app/[short]/page.tsx
-
Determine the target:
- Decode the short path with
decodeURIComponent. - Apply
SHORT_REWRITESif configured. - Base64 encode the decoded short ID for
urls.short_base64. - Fetch the short URL with
redirect: 'manual'to readLocationwithout following malicious content.
- Decode the short path with
-
Run a dry run with the API helper script:
cd apps/api railway run --service API --environment production -- \ node scripts/block-malicious-url.ts \ --dry-run \ --url 'https://zws.im/...' \ --hostname 'example.com' -
Review the JSON output:
shortBase64should be present for a zws short URL.resolvedTargetUrlshould match the malicious redirect target when available.existingShortRoworexistingTargetRowsshould identify rows to block.existingBlockedHostnameshows whether the domain is already blocked.
-
Apply the block:
cd apps/api railway run --service API --environment production -- \ node scripts/block-malicious-url.ts \ --apply \ --url 'https://zws.im/...' \ --hostname 'example.com' -
Verify:
- API should return
410 Gonefor the short URL. - Web URL should not externally redirect to the malicious domain. It may return
200with the blocked URL page. - Redis output should show the domain was added or already present.
- API should return
Verification commands
Use read-only HTTP checks after applying:
node --input-type=module <<'EOF'
const url = 'https://zws.im/...';
const api = new URL(url);
api.hostname = 'api.zws.im';
const [web, apiResponse] = await Promise.all([
fetch(url, { redirect: 'manual' }),
fetch(api, { redirect: 'manual' }),
]);
console.log(JSON.stringify({
web: { status: web.status, location: web.headers.get('location') },
api: {
status: apiResponse.status,
location: apiResponse.headers.get('location'),
body: await apiResponse.text(),
},
}, null, 2));
EOF
Expected API result:
{
"status": 410,
"location": null
}
Troubleshooting
psql not found: use the Node helper script with thepgdependency inapps/api.Cannot find package 'pg': run fromapps/apiand ensure workspace dependencies are installed.- Local
.envhas the wrong DB: userailway run --service API --environment production -- ...; do not trust local row counts for production. - Railway MCP unauthorized: use the Railway CLI if
railway statusworks. railway runheredoc times out: usenode -eor the helper script path instead of heredocs.SELF_SIGNED_CERT_IN_CHAIN: try the helper through Railway production env first. For direct local DB testing,ssl: { rejectUnauthorized: false }may be required.- SQL syntax error near
): usually shell interpolation ate$1placeholders. Use the helper script instead of inline SQL. - Public web returns
200after blocking: this can be correct if it renders the blocked page. Verify the API returns410. - DB says blocked but behavior has not changed: update Redis set
blocked-hostnamesor wait for the 30-minute cache expiry.
Success criteria
- Target hostname exists in
blocked_hostnames. - Target short URL row is
blocked = truewhen a short URL was provided. - Redis set
blocked-hostnamescontains the hostname for immediate enforcement. - API returns
410 Gonefor the malicious short URL. - Final response summarizes what was blocked and the proof from verification.