AI 资讯
How to audit a free AI visibility score with six manual checks
A free AI visibility score is auditable only when you can inspect the prompt, engine, raw answer, date, and denominator. Treat the score as a test result, not a property of your brand. This tutorial builds a six-check control you can run by hand, store as plain data, and compare with any tool's output. The workflow takes three buyer questions, runs them in two AI surfaces, and records the six answers without trying to force agreement. It will not estimate your entire market. It will tell you whether a dashboard's headline number has enough evidence to be investigated. What does an AI visibility score measure? An AI visibility score usually summarizes brand presence across a defined set of generated answers. That definition contains the trap: the question set is part of the metric. So are the engine panel, run date, session state, retrieval mode, and rule used to count a “hit.” Remove those inputs and the number is not reproducible. Imagine a tool asks three questions in two engines. That creates six cells. If your brand appears in two cells, the simple presence result is: presence = brand_present_cells / total_cells presence = 2 / 6 presence = 0.333... = 33.3% The arithmetic is trivial. The evidence is not. A different tool can ask five different questions in three engines and produce a different score without contradicting the first run. The two tools measured different grids. Keep the unit explicit: “present in two of six generated answers on this date” is defensible. “Our AI visibility is 33” is incomplete. Which evidence fields should you require? Require five fields for every result: prompt, engine, raw answer, timestamp, and counting rule. Use a sixth field for cited sources when the surface exposes them. A source-only appearance and a prose mention can signal different problems, so do not merge them silently. Here is one real saved result from Webappski's public 14 June 2026 tracker report: { "run_date" : "2026-06-14" , "prompt" : "beste Answer Engine Optimiz
开发者
Why a live payment is not a release test
Why a live payment is not a release test The riskiest way to test a SaaS checkout is to make a real payment to yourself. It feels reassuring: the live checkout opened, the card worked, the webhook fired and the refund came back. But that proof mixes engineering QA with revenue evidence. Three different proofs A cleaner billing release process separates three questions: Does billing behave correctly? Test payment, refund, webhook and subscription edge cases in a Stripe sandbox. Is production configured correctly? Verify the live price, currency, checkout destination, webhook configuration and deployed revision without moving money. Did a customer pay? Treat a genuine live transaction as customer activity and revenue evidence, not as an engineering fixture. Stripe documents sandboxes as isolated testing environments and separates sandbox credentials from live credentials. The practical lesson is broader than Stripe: operational proof and commercial proof should not share the same transaction. A useful boundary Use this sequence: Sandbox QA → read-only production verification → genuine customer payment . It keeps release evidence, reconciliation and revenue numbers easier to interpret. We recently tightened the same boundary in VendorOS. That does not prove live customer revenue; it is a workflow lesson about keeping evidence categories separate. If your release process still requires a live self-payment, ask which part of the verification can become read-only. Sources: Stripe Sandboxes Stripe API keys Stripe testing VendorOS release boundary
AI 资讯
Build a JSON-RPC 2.0 API in Symfony in 15 minutes: from composer require to OpenAPI
REST works great while your API describes resources. But as soon as the domain becomes verb-shaped - recalculateInvoice , mergeAccounts , assignTask - you end up bending verbs into nouns and arguing about which HTTP method cancels an order. JSON-RPC 2.0 cuts through all of that: every call is just method + params , one endpoint, a spec that fits on two pages, and batching out of the box. In this article we will build a working JSON-RPC 2.0 API on Symfony: a task tracker with DTO validation, batch requests and generated OpenAPI documentation. There is surprisingly little code to write: methods are declared with attributes, validation is derived from property types, and Swagger is generated by a console command. Everything below lives as a ready-to-run project on GitHub: symfony-jsonrpc-api-demo - clone it and poke it with curl while you read. We will use the otezvikentiy/json-rpc-api bundle (PHP 8.2-8.5, Symfony 6.4/7/8; this article uses PHP 8.4 and Symfony 7.4). Full disclosure: I am the author of the bundle. It has been running in production for three years - internal fintech tooling, an HRM system - nothing glamorous load-wise, but the correctness, logging and audit requirements were real, and they shaped most of what you will see below. Installation composer create-project symfony/skeleton: "7.4.*" tasks-api cd tasks-api composer require otezvikentiy/json-rpc-api If Flex has contrib recipes enabled, the bundle registers itself. If not, it is two lines by hand: // config/bundles.php return [ // ... OV\JsonRPCAPIBundle\OVJsonRPCAPIBundle :: class => [ 'all' => true ], ]; Wire up the route and a minimal config: # config/routes/ov_json_rpc_api.yaml ov_json_rpc_api : resource : ' @OVJsonRPCAPIBundle/config/routes/routes.yaml' # config/packages/ov_json_rpc_api.yaml ov_json_rpc_api : access_control_allow_origin_list : - ' http://localhost:8000' The bundle registers a single route, /api/v{version} - every request goes through it. Note the CORS list format: these are ful
开发者
Download Multiple Files as a ZIP in React — Including Multi-GB Archives
A “Download all as ZIP” button in React starts simple. A production version also needs progress, cancellation, retry, useful errors, and a plan for archives that are too large for browser memory. In this tutorial, we’ll use Eazip , an open-source ZIP toolkit for JavaScript and React. Its React package gives you a hook for starting ZIP jobs and a ready-made tray for showing their status. Everyday files can be zipped entirely in the browser. When the same feature needs to handle multi-GB archives or thousands of remote URLs, it can move the job to Eazip Cloud without adding any backend code. Install the React package npm install @eazip/react @eazip/react requires React 18 or later. It includes the core ZIP engine, so you do not need to install another Eazip package. Build a working ZIP download component This component lets a user select several files and download them as one ZIP: import { useState } from ' react ' ; import { EazipTray , useEazip } from ' @eazip/react ' ; export function FileZipDownload () { const [ files , setFiles ] = useState < File [] > ([]); const zip = useEazip (); return ( < section > < label > Files to download < input type = "file" multiple onChange = { ( event ) => setFiles ( Array . from ( event . currentTarget . files ?? [])) } /> </ label > < button type = "button" disabled = { files . length === 0 || zip . isBusy } onClick = { () => zip . download ({ files , zipName : ' selected-files.zip ' , }) } > Download { files . length || '' } files as ZIP </ button > < EazipTray /> </ section > ); } There are three Eazip pieces in this example: useEazip() gives the component its download commands and current task. zip.download() starts the ZIP job and returns immediately. <EazipTray /> shows progress, cancel, retry, partial results, errors, and the completed download. No provider or CSS import is required. What happens to the selected files? Without a strategy option, Eazip uses its Local strategy. The selected File objects stay on the user’s devi
AI 资讯
Adults don't want parental control apps. They want a wall they choose themselves
I keep seeing the same mismatch in the screen-time category: a lot of apps are technically blockers, but they feel like parental control software. That is fine if a parent is the customer. It is a bad fit if the user is an adult trying to manage their own habits. The difference is not cosmetic. When a blocker feels like surveillance, adults bounce. They do not want an account, a dashboard, or the feeling that their phone behavior is being watched somewhere else. That is the gap I built SproutGuard for: built for adults blocking themselves , not kids runs on-device through Apple's Screen Time APIs no account no server no usage data leaving the phone App Store: https://apps.apple.com/us/app/sproutguard-screen-time-detox/id6768664921?ct=devto-adults I also put the positioning plainly on the product page: self-control, not parental control The hard lesson from launching it is that being right about the problem is not the same thing as being shareable . Privacy architecture matters, but users rarely tell friends about architecture. What they do share is something emotional or visible: a streak, a mascot, a challenge, a before/after feeling. So the current working question for me is not "how do I explain on-device privacy better?" It is: How do you make a self-control product feel human enough that people talk about it? Website: https://shantj.github.io/sproutguard/ If you've worked on consumer productivity or habit products, I'm interested in what actually made users talk about them.
AI 资讯
Silent Retries and Agent Latency: What Sentry's Span Hierarchy Taught Us About Multi-Agent Observability
Sarvar's post about discovering a hidden retry in a 5-agent pipeline (one agent taking 22.6s while others took 5s) is a perfect case study in why observability infrastructure matters for agentic systems. Here's what jumped out: Agent-as-black-box is dangerous. When you string together multiple agents, you lose visibility into retry logic, backoff strategies, and cascade failures unless you instrument at the span level. The latency wasn't in the agent logic itself; it was in the retry envelope. Span hierarchy exposes the invisible. Sentry's approach of grouping spans hierarchically made the problem visible at a glance. Without it, you'd see "agent took 22.6s" and assume it was compute-bound. With hierarchy, the retry pattern was obvious. This scales badly across agents. In a 5-agent system, one bad retry strategy can block or cascade. Add error handling, timeout logic, and fallback chains, and you're building a retry forest no one fully understands. The observability debt compounds. The fix is cheap, the insight is priceless. Once Sarvar knew what was happening, tuning retry counts or backoff curves took minutes. The time cost was finding it. Takeaway: If you're building multi-agent systems, instrument early. Span-level observability isn't optional; it's the difference between "it's slow" and "here's why, and here's the fix."
AI 资讯
8051 What does SDCC do part 1 ?
1. Introduction and Problem Statement A good way to learn what a compiler really does when transforming a C source code into a binary is to disassemble the binary and compare it with the C source code. It is especially true for 8 bits microcontrollers like the 8051. In order to test SDCC we are going to use the following C source code. /* ========================================================================== * * Universal Test Corpus - Heterogeneous Architecture Analysis * * ========================================================================== */ #include <stdint.h> // 1. Global variables (testing absolute/relative addressing modes) volatile uint32_t global_var_32 = 0xDEADBEEF ; volatile uint8_t global_var_8 = 0x42 ; const char string_const [] = "TARGET_STRING" ; // 2. Function with parameter passing and local variables (stack / Frame Pointer test) int32_t callee_function ( int16_t a , int16_t b ) { volatile int32_t local_result = 0 ; // Basic and mixed arithmetic operations (8, 16, 32 bits) local_result += ( int32_t )( a * b ); local_result -= ( int32_t )( a / ( b | 1 )); // Avoid division by zero // Shift tests and logical operations (highly variable depending on ISAs) local_result = ( local_result << 2 ) ^ 0x55AA55AA ; local_result = ( local_result >> 1 ) | ( int32_t ) global_var_8 ; return local_result ; } // 3. Main function grouping complex control flows int main ( void ) { volatile int32_t accumulator = 0 ; int16_t i ; // Loop test (Conditional jumps, decrement, comparison tests) for ( i = 0 ; i < 10 ; i ++ ) { if ( i == 5 ) { accumulator += 100 ; } else { accumulator += i ; } } // Multiple branching test (Switch / Jump Table or cascaded if-else) switch ( global_var_8 ) { case 0x10 : accumulator += 10 ; break ; case 0x20 : accumulator += 20 ; break ; default: accumulator -= 5 ; break ; } // Function call (Stack management, save registers Link Register/PC) accumulator += callee_function (( int16_t ) accumulator , 3 ); // Pointer and indirect memory ac
产品设计
YouTube is making it harder to earn money on YouTube
Starting February 1st, 2027, creators who want to monetize their channel through YouTube's Partner Program (YPP) will need at least 1,000 subscribers and either 8,000 qualified watch hours over the past year, or 20 million qualified Shorts views in the last 90 days. That's a sizeable jump from YouTube's current requirement of 1,000 subscribers with […]
AI 资讯
How to Build a Tableau Dashboard and Story
By the end of this guide you will have a published Tableau dashboard and a three-point story, built on a real dataset. It lives on a public URL you can put in an application. You build four small sheets. Each one makes exactly one point. You arrange them on a single screen, then walk a reader through them in three steps that end with a recommendation. Every step says what to click and what you should see afterwards. Four small sheets, rather than a wall of charts, because a dashboard has to argue for something. A screen holding everything you could build leaves the reader to work out what matters. Most readers will not do that work. Dashboard vs Story, in one line. A dashboard puts several charts on one screen so someone can explore. A story is a sequence of views with captions, clicked through in order, so someone is walked to a conclusion. Build both: the dashboard is what a hiring manager glances at, the story is what proves you can think. The original carries a diagram here. In words: Four separate worksheets stack on the left: a big single number, a set of vertical bars, a set of horizontal bars, and a scatter of circles. An arrow points right to one dashboard panel that holds all four of them arranged on a single screen: the number across the top, the two bar charts side by side in the middle, the scatter along the bottom. A second arrow points right to three story cards numbered one, two and three, each showing one of those views with a caption line above it. The worked example. Every instruction below is written against a real, free dataset: the Telco Customer Churn file on Kaggle, 7,043 customers, one row each. A finished analysis of it, including the Python script that shapes the data, is public at telco-churn-analysis . Swap in your own dataset and the steps do not change, only the field names do. Step 1: Shape the data before you open Tableau Tableau is a display layer. Deriving something inside it takes longer than deriving it upstream in SQL, Python or
AI 资讯
Technical Tenacity: What to Do When the Tools Fight Back
This guide gives you a repeatable loop for the days when nothing works, and four true stories showing it used on real problems. Here is what a working day actually contains. A website's firewall blocks you for no reason. A table that visibly exists tells your script it does not. A query runs for thirty minutes with no end in sight. A fix you know is correct changes nothing at all. None of that means you are doing it wrong. That is the job. What separates people who ship analyses from people who stop is technical tenacity : staying methodical when the tools fight back. It is not a personality trait you either have or lack. It is a small procedure, and you can learn it in the next ten minutes. The diagnosis loop (tenacity is a method, not a mood) Think back to the last time a tool beat you for an hour. What was the first thing you did when it failed, and what did you do second? Most people can name the first move and not the second, and the second is where the method lives. Gritting your teeth and re-running the same thing harder is not tenacity; it's frustration with extra steps. What experienced people actually run is a loop: Step Move 1. Read the actual message Not "it's broken" — the words. Error messages name the symptom precisely, even when the cause is elsewhere. 2. Form ONE hypothesis "The table isn't in the file the script reads." Specific enough to be wrong. 3. Run the cheapest test of it Prefer checks that take seconds — list the tables, count the rows, print one value. 4. Verify from a second vantage point Don't ask the tool that's confusing you whether it's confused. Check the file from outside, the data from a different program, the value with a different query. 5. Change ONE thing, re-run Change three things and you'll never know which one mattered — or which one broke something new. 6. Timebox, then change strategy If the current approach has eaten 30 minutes with no progress, stopping is a decision, not a defeat. There's usually a second road. Four tr
AI 资讯
COUNT in SQL, Explained for Beginners
COUNT looks like the simplest function in SQL, and it is the one that quietly trips up the most people in interviews and on the job. The confusion is almost always the same: COUNT(*) , COUNT(column) , and COUNT(DISTINCT column) look nearly identical but count three different things. Once you can say out loud what each one counts, a lot opens up. You can verify a data migration, find duplicates, and measure how complete a column is, all with the same little function. This guide is that explanation, with lots of small examples you can copy. The one-sentence version. COUNT(*) counts rows . COUNT(column) counts rows where that column is not NULL . COUNT(DISTINCT column) counts how many different non-NULL values that column has. Everything below is just that sentence, slowed down. The three forms of COUNT and what each one counts Picture one small table, customers , with a region column where two rows were never filled in: id name region 1 Maya North 2 Jordan South 3 Alex North 4 Sam NULL 5 Taylor NULL Now run the three forms on it: SELECT COUNT(*) AS all_rows, COUNT(region) AS rows_with_region, COUNT(DISTINCT region) AS different_regions FROM customers; all_rows rows_with_region different_regions 5 3 2 COUNT(*) = 5. Every row, no exceptions. The * means "the row itself," so NULLs never matter. COUNT(region) = 3. Only the rows where region has a value. Sam and Taylor are skipped because their region is NULL. COUNT(DISTINCT region) = 2. The different values are just North and South . The two Norths collapse to one, and NULL is not counted. The NULL rule that makes them disagree Predict it first. A table has 100 rows. Twenty of them have no email address. What does counting the email column give you? Say the number before you read on. Here is the whole trick in one line: COUNT(*) counts rows. COUNT(something) counts non-NULL values of that something. So the moment a column has any NULLs, COUNT(column) comes back smaller than COUNT(*) . That gap is not a bug, it is informat
AI 资讯
Entity Resolution: One Real Thing, Many Messy Names
This guide walks through five steps for working out which records are the same real thing, and merging them without wrecking your data. It runs on real chart data, and it includes the two times the rules came out wrong. Here is the problem in one example. Count the distinct artists in Billboard's public chart history and the number is wrong. "Elvis Presley" and "Elvis Presley With The Jordanaires" are the same man, and so are five other credit strings. One real-world entity , seven database strings . Every dataset with human-entered names has this. Customers who signed up twice. "IBM" against "I.B.M." against "International Business Machines". The same supplier in two systems, spelled two ways. The work of fixing it is called entity resolution . Matching across two datasets is record linkage . Removing duplicates inside one is deduplication . They are the same skill pointed at different situations, and it is one of the most common tasks an analyst actually gets handed. The vocabulary map Term Meaning Entity The real-world thing: one artist, one customer, one company Entity resolution Figuring out which records refer to the same entity Record linkage The same problem across two datasets. "Is row 5 in file A the same person as row 90 in file B?" Formalized by Fellegi & Sunter (1969) Deduplication The same problem inside one dataset Normalization / standardization Transforming values toward a canonical form (lowercasing, trimming, cutting suffixes) so equal things become equal strings Match key The cleaned column(s) you actually join on Match rate The share of records that found their counterpart. This is the number that keeps the whole exercise honest Clerical review Human eyes on the records the rules could not decide. This is a formal stage of the classic framework, not an admission of failure Step 1: measure the fragmentation before fixing anything The worked example is Billboard Hot 100 history, 1958 to present. The goal is one clean row per artist. Before writing
AI 资讯
The Laravel 13 Features That Matter in Real Projects
The Laravel 13 Features That Matter in Real Projects Laravel 13 shipped on March 17, 2026, and the upgrade story is unusually simple: zero application-level breaking changes from Laravel 12, one hard requirement (PHP 8.3), and several features that are genuinely useful in production rather than just impressive in release notes. This post focuses on the features you will actually reach for on real client projects — not an exhaustive tour. For the full release overview, upgrade checklist, and breaking changes reference, see Laravel 13: Features, Upgrade Guide, and Breaking Changes . Prerequisites: PHP 8.3+, Laravel 13.x (latest stable: 13.14.0 as of June 2026), Composer 2.x. 1. PHP Attributes on Models and Controllers Laravel 13 adds PHP 8-style #[Attribute] support across 15+ framework locations. The old property-based syntax still works — this is purely additive. On Eloquent Models: use Illuminate\Database\Eloquent\Attributes\Table ; use Illuminate\Database\Eloquent\Attributes\Fillable ; use Illuminate\Database\Eloquent\Attributes\Hidden ; #[Table('posts', primaryKey: 'id', incrementing: true, timestamps: true)] #[Fillable('title', 'body', 'user_id')] #[Hidden('deleted_at')] class Post extends Model {} On Controllers: use Illuminate\Routing\Attributes\Controllers\Authorize ; use Illuminate\Routing\Attributes\Controllers\Middleware ; #[Middleware('auth')] class CommentController extends Controller { #[Middleware('subscribed')] #[Authorize('create', [Comment::class, 'post'])] public function store ( Post $post ) { } } When to actually use this: Attributes shine on large domain models where $fillable , $hidden , $casts , and relationship declarations are scattered across the class. Collocating table definition and mass assignment rules at the top of the file improves readability at a glance. On small CRUD models, the tradeoff is extra import lines for minimal gain. Common mistake: Mass-converting every existing model to attribute syntax in a single PR. It creates a lar
科技前沿
Why Each Octopus Arm Has a Mind of Its Own
Two-thirds of an octopus’s neurons are in its arms—each operating independently—including the one it uses to have sex.
AI 资讯
Build a React client intake form with file uploads
Client intake often requires two types of information: searchable answers and files for review. This Vite and React example collects both in the same response. You can try the form without an account. Ask only what you need The example asks for: the client's name; a work email address; the result they need; an optional target date; up to five briefs or reference files. Each answer should help someone prepare for the first call. Leave detailed discovery questions for the call. Define the form The form schema lives in the React app: import { createClient , defineForm , FilloForm } from " @usefillo/react " ; const intake = defineForm ({ id : " vite-client-intake " , title : " Tell us about your project " , description : " Tell us what you need, when you need it and which files will help us prepare. " , pages : [ { id : " intake " , blocks : [ { id : " name " , kind : " short_text " , label : " Your name " , required : true }, { id : " email " , kind : " email " , label : " Work email " , required : true }, { id : " outcome " , kind : " long_text " , label : " What result do you need? " , required : true , }, { id : " target-date " , kind : " date " , label : " Target date " }, { id : " documents " , kind : " file_upload " , label : " Briefs or reference files (PDF, DOCX, PNG or JPG) " , accept : [ " .pdf " , " .doc " , " .docx " , " .png " , " .jpg " , " .jpeg " ], maxFiles : 5 , }, ], }, ], settings : { submitLabel : " Send project details " }, }); Keep the form and field IDs after you collect the first response. Fillo uses them as stored answer keys. You can change labels and help text without changing the IDs. The React app controls the route, layout, styles and what happens after submit. Fillo handles the schema, validation, uploads and responses. The SDK renders React controls in the page. It does not use an iframe. Send files straight to storage The browser sends each file to the storage connected to the Fillo workspace. The Vite app does not proxy the file throu
AI 资讯
Nodes and Networks: How Blockchains Actually Stay Decentralized
When someone says "Bitcoin has over 15,000 nodes worldwide," they mean 15,000+ independent computers are each running Bitcoin software and each maintaining their own full copy of the blockchain. No server owns the truth. Every node checks it for itself. That single fact — every node independently verifies every transaction and block against protocol rules — is the reason blockchains don't need a central authority. If one node tries to cheat, the rest simply ignore it. There's no admin account to compromise because there's no admin. Not All Nodes Do the Same Job Full Node Downloads and stores the entire blockchain, every block since genesis, and independently validates everything against consensus rules. Highest security ~500 GB for Bitcoin ~1 TB for Ethereum This is the backbone of network security. A full node doesn't trust anyone's summary of the chain; it recomputes validity itself. Light Node (SPV) Stores only block headers, not full transaction data. Uses Merkle proofs and relies on full nodes to verify transactions. Low storage, ~50 MB Trusts full nodes for verification What most mobile wallets run Mining/Validator Node A full node that also participates in block creation. Miners (Proof of Work) solve computational puzzles; validators (Proof of Stake) stake cryptocurrency as collateral. Both earn rewards for securing the network. Creates new blocks Earns rewards Requires specialized hardware (PoW) or capital at stake (PoS) Archive Node Everything a full node stores, plus historical state at every block height. Complete history ~15+ TB for Ethereum Used by explorers, analytics platforms, and enterprise tooling Why Peer-to-Peer Instead of Client-Server A traditional web service is client-server: your browser requests data from a company's servers. If those servers go down, the service is unavailable. That's a single point of failure by design. Blockchain networks use peer-to-peer (P2P) architecture instead. Every participant is simultaneously a client and a serv
开发者
Geo-Blocking: Block Malicious Traffic from Specific Countries (2-Minute Setup)
Why Geo-Block? Not every country needs to reach your server. If you run a local business in Brazil, you don't need traffic from North Korea. If you serve customers in the EU, you probably don't need visitors from 150 other countries hitting your login page. Geo-blocking at the WAF level stops unwanted traffic before it ever reaches your application. No CPU spent. No database queries wasted. No bandwidth consumed. The Numbers from My Server After 30 days of logging, I checked where attacks came from: Traffic Source % of Total Requests % of Attacks Target countries (where my customers are) 23% 8% Non-target countries 77% 92% 77% of my traffic came from countries I don't serve, and 92% of attacks originated from those countries. Geo-blocking the non-target regions would eliminate the vast majority of malicious traffic with zero impact on real users. Setting Up Geo-Blocking in SafeLine Step 1: Go to IP Groups -> Geo Blocking in the dashboard. Step 2: Choose your approach: Option A: Allow-list mode (strictest) Block everything, then whitelist specific countries. Block : ALL Allow : United States , Canada , United Kingdom , Germany , France , Netherlands Option B: Block-list mode (targeted) Allow everything, then block specific high-noise regions. Block : Russia , China , Vietnam , North Korea , Iran Step 3: Apply the rule. Done. What Happens to Blocked Visitors Blocked IPs see a 403 Forbidden page. They can't reach your application at all — the WAF drops the connection at the proxy layer. Your app server never sees these requests. SafeLine logs every geo-blocked request to Attack Logs. You'll see: Which country the IP was from What URL they tried to access The exact timestamp Which Countries to Block Based on my 30-day log analysis and common community reports: Almost always safe to block: North Korea — 0 legitimate traffic for 99.9% of sites Iran — heavy scanner activity, minimal legitimate traffic (for non-Iranian sites) High scanner volume, consider blocking if not yo
AI 资讯
How to Set Up Rate Limiting on Any Web App (Free, No Code Changes)
The Problem Your login page, search endpoint, or contact form is getting hammered. Rate limiting is the fix — but implementing it in application code means finding every endpoint, writing middleware, choosing a storage backend, and deploying changes. On a WAF, you set it once and it applies everywhere. Why WAF-Level Rate Limiting Is Better Approach Code-Level WAF-Level Setup time Hours to days 5 minutes Code changes Required None Applies to One endpoint at a time All routes with one rule Storage Redis/Memcached needed Built into WAF Performance impact Hits your app server Blocked at proxy Updates Deploy new code Change a rule in dashboard Step-by-Step: Rate Limit Setup 1. Log into SafeLine Dashboard Go to https://<your-ip>:9443 . Navigate to Rules -> Add Rule -> Rate Limiting. 2. Create Your First Rule — Login Protection Name: Login brute force protection Match: URL contains /login OR /wp-login.php OR /auth Limit: 5 requests per minute per IP Action: Block (return 429 Too Many Requests) Block duration: 15 minutes This stops credential stuffing cold. An attacker who tries 5 wrong passwords in 60 seconds gets blocked for 15 minutes. That's a maximum of 480 attempts per day — vs unlimited without rate limiting. 3. Search Endpoint Protection Name: Search rate limit Match: URL contains /search OR /query Limit: 30 requests per minute per IP Action: Challenge (JS captcha) Search endpoints are expensive. A single user running a script can do 1,000+ queries per minute and degrade performance for everyone. 30/min is generous for humans but stops scripts. 4. Global Baseline Name: Global request limit Match: /* Limit: 300 requests per minute per IP Action: Throttle Catches anything that slips through specific rules. 300/min = 5/sec, which is more than any human needs. What Happens When a Limit Is Hit SafeLine logs every rate limit trigger to the Attack Log. You'll see: Which IP triggered it Which endpoint they were hitting Time of the trigger Whether they got blocked, challenge
AI 资讯
How to stop a Claude Code agent writing outside a directory
When you're sitting in front of an agent, "don't touch anything outside src/ " is enforced by you noticing. Unattended, it has to be enforced by something that runs whether or not anyone is watching. Claude Code gives you two mechanisms for that, and they are not interchangeable. One is declarative and can't express what you probably want. The other can, but is structurally blind to a whole category of writes. Here's what each one actually does, and the code for the second. Why permissions.deny isn't enough Permission rules live in settings.json and take the form Tool(specifier) : { "permissions" : { "deny" : [ "Read(./.env)" , "Read(./.env.*)" , "Write(./.github/**)" , "Write(//etc/**)" ] } } Paths are gitignore-style. A leading // means absolute, ~ means home, and anything else is relative to the settings file. deny beats ask , which beats allow , and rules merge across scopes rather than override — so a deny in project settings still applies even when your personal ~/.claude/settings.json allows the same thing. That precedence is the useful part: a deny rule is hard to undo by accident. The problem is shape. What you want for an unattended agent is an allow-list — only these directories, nothing else. What deny gives you is a block-list, and you cannot build the first out of the second. The obvious trick of denying everything and allowing back the exceptions fails on exactly the precedence rule that makes deny valuable: Write(**) in deny outranks every allow you pair it with, so the agent can write nothing at all. Claude Code does have one allow-list-shaped boundary — the project root, plus whatever you list in additionalDirectories . That stops an agent wandering into /etc . It says nothing about which directories inside your project it may write, which is usually the interesting question. Nobody's real worry is that a scheduled agent edits /etc/hosts . It's that the agent tasked with writing articles decides to fix its own scheduling config. So for anything fin
AI 资讯
Topic selected: Option A – Purely Technical: "Building a Secure AI Proxy for Browser Tools
This is the strongest choice. It teaches a tangible, highly demanded skill (API key security) with actual code, making the backlink to AfriWidget feel like a natural, neutral citation rather than a sales pitch. Here is the article, rewritten to be strictly technical, objective, and genuinely useful for dev.to readers. Stop Exposing Your AI API Keys: Build a Secure Proxy with Cloudflare Workers We have all seen it. You open the browser's DevTools on a "cutting-edge" AI startup's landing page, check the Network tab, and find a direct POST request to api.openai.com containing a plaintext API key in the headers. It is one of the most common—and dangerous—mistakes in modern web development. Exposing your LLM API key client-side is an open invitation for abuse, leading to stolen credits, hefty bills, and potential account suspension. The standard solution is the Backend-for-Frontend (BFF) proxy pattern. But how do you implement it practically, cheaply, and securely without spinning up a heavy Express server? In this guide, I will walk you through building a lightweight, serverless AI proxy using Cloudflare Workers to securely call Groq (or OpenAI) APIs from your browser-based calculators and tools. The Architecture: How It Works Instead of your frontend talking directly to the AI provider, we introduce a stateless middleware layer: Browser App → Cloudflare Worker (Proxy) → Groq/OpenAI API ↑ ↑ (No API Key) (API Key stored securely in Worker env vars) The Worker's responsibilities: Receive the sanitized calculation context from the frontend (numbers, not PII). Attach the secret API key via environment variables. Forward the request to the LLM provider. Stream or return the generated insight back to the client. Step 1: Scaffolding the Cloudflare Worker We will use the new create-cloudflare CLI. Make sure you have Node.js installed. npm create cloudflare@latest ai-proxy Choose "Hello World" worker and TypeScript. Once inside the directory, install the Groq SDK: npm install gr