2026-03-14 00:11:33
Crypto hit a strange paradox in 2025. On one hand, institutional roots deepened and user bases hit historic highs, with Binance crossing the 300 million registered users mark. On the other hand, the industry faced the industrialization of cybercrime. Web3 suffered roughly $4 billion in total losses last year, and more than half, over $2 billion, went directly to actors linked to North Korea.
We are seeing a move away from simple code exploits toward something far more dangerous. State-sponsored groups are now hitting centralized infrastructure with a level of precision that mirrors military operations. This is a distinct turning point. We have moved past the era of random, opportunistic smash-and-grabs. Instead, we are now witnessing organized financial extraction operating at a global industrial level.
The numbers from 2025 reveal a disturbing efficiency in state-backed cybercrime. North Korea-linked groups stole a minimum of $2.02 billion, according to findings from Hacken's 2025 Security Report and the Chainalysis 2026 Crypto Crime Report. Despite executing fewer individual attacks, the value of funds stolen by these actors rose 51% year-over-year. With one nation-state driving 52% of global Web3 losses, cyber theft has evolved into a critical revenue engine for the regime.

The tactics employed by groups like Lazarus have shifted away from the scattershot approach of previous years. In 2025, these actors engaged in "Big Game Hunting," moving their focus from decentralized finance bridges to centralized infrastructure where larger pools of liquidity reside.
The compromise of Bybit stands as the year's defining outlier event, accounting for nearly $1.5 billion of the total losses. This single breach skewed the year's security data significantly, pushing the ratio between the largest hack and the median hack to over 1,000 times, according to Chainalysis.
These breaches rarely start with code exploits; they start with people. Brute-force hacking is out and social engineering is in. North Korean operatives have pivoted to using fake recruiter profiles and IT worker scams to physically or digitally embed themselves within crypto firms. Sometimes they get hired; other times, they trick executives into downloading malware.
By compromising the people who hold the keys to internal infrastructure, they render traditional firewalls useless. Human manipulation has become their most lethal tool.
Stealing billions in digital assets is only the first phase of the operation; converting those assets into usable fiat currency without detection is the second. North Korean actors have developed a highly complex laundering pipeline to address this challenge. TRM Labs identifies this network as the Chinese laundromat, a system where DPRK actors outsource the liquidation process to a web of over-the-counter (OTC) brokers and underground banking channels.
These brokers often settle transactions in CNY or physical goods, effectively bypassing blockchain tracing mechanisms that track on-chain movement. The laundering process is methodical and patient.
Once funds are taken, they enter a complex 45-day laundering cycle. Chainalysis data maps the route these assets take, a complex journey through mixers, cross-chain bridges, and swap protocols, before they eventually exit the crypto ecosystem. It functions less like a getaway car and more like a sophisticated and transnational pipeline. The goal is to wash illicit funds so thoroughly through the global economy that tracing them back to the source becomes nearly impossible.
Combating this level of threat requires a fundamental pivot in how the industry approaches security. The focus must shift from solely auditing smart contract code to auditing operational security and human processes. As attacks target personnel and internal infrastructure, defense mechanisms must evolve to protect the human layer.
Major players are responding to these threats at scale. With over 300 million users, Binance provides a clear example of how to harden defenses against state actors. According to its 2025 Year in Review, the exchange stopped $6.69 billion in potential fraud, shielding more than 5 million users. Much of this success came from internal Red Team simulations that trained staff to spot social engineering attempts. The impact was immediate as employee phishing failure rates fell from 3.2% in 2024 to only 0.4% in 2025.
Noah Perlman, Chief Compliance Officer at Binance, emphasizes that security improvements are quantifiable even amidst massive growth.
"Analysis of independent industry data shows a steep reduction in our direct illicit exposure between early 2023 and mid-2025," Perlman states, noting that this decline occurred despite the platform remaining the primary venue for global liquidity. He adds that this reduction was sustained "even as Binance handled volumes comparable to the next six largest exchanges combined."

The data confirms that trust by design goes beyond a simple marketing buzzword—it's an operational necessity. Platforms can only effectively narrow the state-sponsored attack surface by running real-time threat detection in tandem with strict compliance protocols.
A review of the 2025 figures shows a diverging pattern. While state-sponsored actors are stealing larger sums, the industry is simultaneously closing the gap in detection and prevention. With North Korean actors driving such massive losses, the market has learned that operational resilience requires more than just auditing code.
As cryptocurrency continues its integration into the global financial system—evidenced by the surge in institutional adoption—security ceases to be merely a technical feature. It becomes the foundational requirement for the future of finance.
\
:::tip This story was distributed as a release by Jon Stojan under HackerNoon’s Business Blogging Program.
:::
\
2026-03-14 00:03:16
How are you, hacker?
🪐 What’s happening in tech today, March 13, 2026?
The HackerNoon Newsletter brings the HackerNoon homepage straight to your inbox. On this day, Microsoft Goes Public in 1986, and we present you with these top quality stories. From 7 Iconic 20th-Century Ad Campaigns and What Todays Marketers Can Learn From Them to Milliseconds Make Millions: How and Why to Speed Up Your App, from InDrives Playbook, let’s dive right in.

By @proofofusefulness [ 3 Min read ] Real utility over hype. Meet InfoFusion Hubs, Firefox Managed Session Controller, and NoteOCR — this weeks standout Proof of Usefulness projects. Read More.

By @mattleads [ 14 Min read ] Learn to implement Apple Passkeys in Symfony 7.4. Secure, passwordless auth guide using PHP 8.x attributes and the web-auth/webauthn-symfony-bundle. Read More.
![]()
By @yuliiam [ 9 Min read ] Seven iconic advertising campaigns—from Volkswagen to Apple—that changed marketing forever and still offer powerful lessons for modern marketers. Read More.

By @nishantbhanot [ 8 Min read ] Discover why Physical AI requires superhuman capability. Learn how 360° sensor fusion outperforms human limits in fog, darkness, and spatial awareness. Read More.

By @indrivetech [ 14 Min read ] How faster mobile app startup improves revenue. Practical iOS performance techniques, metrics, and real optimization results from inDrive. Read More.
🧑💻 What happened in your world this week?
It's been said that writing can help consolidate technical knowledge, establish credibility, and contribute to emerging community standards. Feeling stuck? We got you covered ⬇️⬇️⬇️
ANSWER THESE GREATEST INTERVIEW QUESTIONS OF ALL TIME
We hope you enjoy this worth of free reading material. Feel free to forward this email to a nerdy friend who'll love you for it.See you on Planet Internet! With love, The HackerNoon Team ✌️

2026-03-14 00:00:17
Welcome to HackerNoon’s Projects of the Week, where we spotlight standout projects from the Proof of Usefulness Hackathon, HackerNoon’s competition designed to measure what actually matters: real utility over hype. \n \n Each week, we’ll highlight projects that demonstrate clear usefulness, technical execution, and real-world impact - backed by data, not buzzwords.
This week, we’re excited to share three projects that have proven their utility by solving concrete problems for real users: InfoFusion Hubs, Firefox Managed Session Controller, and NoteOCR.
\
:::tip Want to see your own project spotlighted here?
Join the Proof of Usefulness Hackathon to get on our radar.
:::
\
\ InfoFusion Hubs is a multi-niche blogging platform covering artificial intelligence, technology, web development, SEO, and digital marketing. The platform focuses on well-researched, easy-to-understand content that helps readers stay updated with modern tech trends.
\ InfoFusion Hubs exists as the rapid evolution of AI and digital tools creates a massive need for accessible, beginner-friendly educational resources that bridge the gap between technical complexity and everyday utility.
Proof of Usefulness score: +14 / 1000

\
:::tip See InfoFusion Hubs’ full Proof of Usefulness report
Read their story on HackerNoon
:::
\
\ Firefox Managed Session Controller is an open-source technical solution designed to help parents manage internet access on Ubuntu systems. With a focus on digital wellness, this project aims to protect children from unrestricted internet exposure while allowing them to leverage technology for work and learning.
Proof of Usefulness score: +36 / 1000

\
:::tip See Firefox Managed Session Controller’s full Proof of Usefulness report
Read their story on HackerNoon
:::
\
\ NoteOCR is a productivity tool designed to help students and professionals convert handwritten notes, tables, and scanned documents into editable digital files.
\ Using the app, users can upload handwritten notes, tabular data, and scanned documents and have them converted into editable digital documents. It is intended to assist students and professionals in reducing hours of manual typing while increasing productivity.
Proof of Usefulness score: +36 / 1000

\
:::tip See NoteOCR’s full Proof of Usefulness report
Read their story on HackerNoon
:::
\
It's our answer to a web drowning in vaporware and empty promises. We evaluate projects based on: \n ▪️ Real user adoption \n ▪️ Sustainable revenue \n ▪️ Technical stability \n ▪️ Genuine utility \n \n Projects score from -100 to +1000. Top scorers compete for $20K in cash and $130K+ in software credits.
You’ll be in good company. The hackathon is backed by teams who ship production software for a living - Bright Data, Neo4j, Storyblok, Algolia, and HackerNoon.
\
:::warning P.S. The clock is ticking - Only 3 months and 3 prize rounds remaining! Don't leave money on the table - get in early!
:::
\

\
1. Get your free Proof of Usefulness score instantly \n 2. Your submission becomes a HackerNoon article (published within days) \n 3. Compete for monthly prizes \n 4. All participants get rewards
Complete guide on how to submit here.
\
:::tip 👉 Submit Your Project Now!
:::
\ Thanks for building useful things! \n P.S. Submissions roll monthly through June 2026. Get in early!
\ \ \ \
2026-03-14 00:00:05
In the modern web era, passwords are no longer sufficient. They are the root cause of over 80% of data breaches, subject to phishing, reuse, and terrible complexity rules. The industry has spoken: Passkeys are the future.
\ Passkeys, built on the Web Authentication (WebAuthn) and FIDO2 standards, replace traditional passwords with cryptographic key pairs. Your device (iPhone, Android, Windows Hello, YubiKey) stores a private key, while the server only ever sees the public key. No hashes to steal, no passwords to reset, and inherently phishing-resistant.
\ In this comprehensive guide, we will build a 100% passwordless authentication system using Symfony and the official web-auth/webauthn-symfony-bundle. We will eliminate the concept of a password entirely from our application. No fallback, no “reset password” links. Just pure, secure, biometric-backed passkeys.
Passkeys work by replacing a shared secret (password) with a public/private key pair. The private key never leaves the user’s Apple device (iPhone, Mac, iPad), and the public key is stored on your Symfony server.
Run the following command to install the necessary dependencies:
composer require web-auth/webauthn-symfony-bundle:^5.2 \
web-auth/webauthn-stimulus:^5.2 \
symfony/uid:^7.4
\ We use @simplewebauthn/browser via AssetMapper (which provides excellent wrapper functions for the native browser WebAuthn APIs) because Apple Passkeys require a frontend interaction that is best handled via a Stimulus controller in a modern Symfony environment, or you can use React/Vue modules.
This is where our application dramatically diverges from a traditional Symfony app. We are going to strip passwords entirely from the system.
\ Standard Symfony User entities aren’t equipped to store Passkey metadata (like AAGUIDs or public key Cose algorithms). We need a dedicated entity to store the credentials.
Our User entity implements Symfony\Component\Security\Core\User\UserInterface. Noticeably absent is the PasswordAuthenticatedUserInterface.
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, unique: true)]
private ?string $userHandle = null;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private ?string $email = null;
public function __construct()
{
$this->userHandle = Uuid::v4()->toRfc4122();
}
...
}
A single user can have multiple passkeys (e.g., Face ID on their phone, Touch ID on their Mac, a YubiKey on their keychain). We need an entity to store these public keys and their associated metadata.
\ Create src/Entity/PublicKeyCredentialSource.php. This entity must be capable of translating to and from the bundle’s native Webauthn\PublicKeyCredentialSource object.
\ Crucially, we must preserve the TrustPath. Failing to do so destroys the attestation data needed if you ever require high-security enterprise hardware keys.
namespace App\Entity;
use App\Repository\PublicKeyCredentialSourceRepository;
use Doctrine\ORM\Mapping as ORM;
use Webauthn\PublicKeyCredentialSource as WebauthnSource;
#[ORM\Entity(repositoryClass: PublicKeyCredentialSourceRepository::class)]
#[ORM\Table(name: 'webauthn_credentials')]
class PublicKeyCredentialSource extends WebauthnSource
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
}
You must also implement a CredentialSourceRepository that implements Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepository.
namespace App\Repository;
use App\Entity\PublicKeyCredentialSource;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
use Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepositoryInterface;
use Webauthn\Bundle\Repository\CanSaveCredentialSource;
use Webauthn\PublicKeyCredentialSource as WebauthnSource;
use Webauthn\PublicKeyCredentialUserEntity;
class PublicKeyCredentialSourceRepository extends ServiceEntityRepository implements PublicKeyCredentialSourceRepositoryInterface, CanSaveCredentialSource
{
public function __construct(ManagerRegistry $registry, private readonly ObjectMapperInterface $objectMapper)
{
parent::__construct($registry, PublicKeyCredentialSource::class);
}
public function findOneByCredentialId(string $publicKeyCredentialId): ?WebauthnSource
{
return $this->findOneBy(['publicKeyCredentialId' => $publicKeyCredentialId]);
}
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
return $this->findBy(['userHandle' => $publicKeyCredentialUserEntity->id]);
}
public function saveCredentialSource(WebauthnSource $publicKeyCredentialSource): void
{
$entity = $this->findOneBy(['publicKeyCredentialId' => base64_encode($publicKeyCredentialSource->publicKeyCredentialId)])
?? $this->objectMapper->map($publicKeyCredentialSource, PublicKeyCredentialSource::class);
$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush();
}
}
The WebAuthn bundle relies on abstract interfaces to find and persist users and credentials. Our repositories must implement these interfaces.
The UserRepository implements PublicKeyCredentialUserEntityRepositoryInterface. Because we want the bundle to handle user creation automatically during a passkey registration, we also implement CanRegisterUserEntity and CanGenerateUserEntity.
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Uid\Uuid;
use Webauthn\Bundle\Repository\CanGenerateUserEntity;
use Webauthn\Bundle\Repository\CanRegisterUserEntity;
use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface;
use Webauthn\Exception\InvalidDataException;
use Webauthn\PublicKeyCredentialUserEntity;
class UserRepository extends ServiceEntityRepository implements PublicKeyCredentialUserEntityRepositoryInterface, CanRegisterUserEntity, CanGenerateUserEntity
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function saveUserEntity(PublicKeyCredentialUserEntity $userEntity): void
{
$user = new User();
$user->setEmail($userEntity->name);
$user->setUserHandle($userEntity->id);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
public function generateUserEntity(?string $username, ?string $displayName): PublicKeyCredentialUserEntity
{
return new PublicKeyCredentialUserEntity(
$username ?? '',
Uuid::v4()->toRfc4122(),
$displayName ?? $username ?? ''
);
}
...
Apple requires specific “Relying Party” (RP) information. This identifies your application to the user’s iCloud Keychain.
Create or update config/packages/webauthn.yaml:
webauthn:
allowed_origins: ['%env(WEBAUTHN_ALLOWED_ORIGINS)%']
credential_repository: 'App\Repository\PublicKeyCredentialSourceRepository'
user_repository: 'App\Repository\UserRepository'
creation_profiles:
default:
rp:
name: '%env(RELYING_PARTY_NAME)%'
id: '%env(RELYING_PARTY_ID)%'
request_profiles:
default:
rp_id: '%env(RELYING_PARTY_ID)%'
\ WebAuthn is incredibly strict about domains. A passkey created for example.com cannot be used on phishing-example.com. To ensure our application is portable across environments, we define our Relying Party (RP) settings in the .env file.
\ Open .env or .env.local and add:
###> web-auth/webauthn-symfony-bundle ###
RELYING_PARTY_ID=localhost
RELYING_PARTY_NAME="My Application"
WEBAUTHN_ALLOWED_ORIGINS=localhost
###< web-auth/webauthn-symfony-bundle ###
In production, RELYINGPARTYID must be your exact root domain (e.g., example.com), and WebAuthn requires a secure HTTPS context. Browsers only exempt localhost for development.
Passkey registration is a two-step handshake:
Security is paramount. Even though WebAuthn is inherently phishing-resistant, your endpoints are still vulnerable to traditional Cross-Site Request Forgery (CSRF) if left unprotected. We will pass Symfony’s built-in CSRF tokens via headers in our fetch() calls.
\ Assuming you have a standard CSRF helper (like csrfprotectioncontroller.js that extracts the token from a meta tag or hidden input), we inject it into our Passkey controller.
import { Controller } from '@hotwired/stimulus';
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
import { generateCsrfHeaders } from './csrf_protection_controller.js';
export default class extends Controller {
static values = {
optionsUrl: String,
resultUrl: String,
isLogin: Boolean
}
connect() {
console.log('Passkey controller connected! 🔑');
}
async submit(event) {
event.preventDefault();
const username = this.element.querySelector('[name="username"]')?.value;
if (!this.isLoginValue && !username) {
alert('Please provide a username/email');
return;
}
const csrfHeaders = generateCsrfHeaders(this.element);
try {
// 1. Fetch options
const response = await fetch(this.optionsUrlValue, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...csrfHeaders },
body: username ? JSON.stringify({ username: username, displayName: username }) : '{}'
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.errorMessage || 'Failed to fetch WebAuthn options from server');
}
const options = await response.json();
// 2. Trigger Apple's Passkey UI (Create or Get)
let credential;
if (this.isLoginValue) {
credential = await startAuthentication({ optionsJSON: options });
} else {
credential = await startRegistration({ optionsJSON: options });
}
// 3. Send result back to verify
const result = await fetch(this.resultUrlValue, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...csrfHeaders },
body: JSON.stringify(credential)
});
if (result.ok) {
window.location.reload();
} else {
const errorText = await result.text();
alert('Authentication failed: ' + errorText);
}
} catch (e) {
console.error(e);
alert('WebAuthn process failed: ' + e.message);
}
}
}
You need to ensure the routing type for webauthn exists. Create config/routes/webauthn_routes.yaml:
webauthn_routes:
resource: .
type: webauthn
To allow users to log in with their Passkey, we need to configure the Symfony Guard (now the Authenticator system).
\ In config/packages/security.yaml:
security:
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
webauthn:
authentication:
routes:
options_path: /login/passkey/options
result_path: /login/passkey/result
registration:
enabled: true
routes:
options_path: /register/passkey/options
result_path: /register/passkey/result
success_handler: App\Security\AuthenticationSuccessHandler
failure_handler: App\Security\AuthenticationFailureHandler
logout:
path: app_logout
access_control:
- { path: ^/dashboard, roles: ROLE_USER }
Because WebAuthn ceremonies involve AJAX fetch() requests from the frontend, a standard Symfony redirect on failure (e.g., trying to register an email that already exists) will be silently swallowed by the browser, resulting in a frustrating user experience.
\ We implement a custom AuthenticationFailureHandler that returns a clean 401 Unauthorized JSON response when the request is AJAX.
\ Create src/Security/AuthenticationFailureHandler.php:
namespace App\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\SecurityRequestAttributes;
readonly class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
{
public function __construct(private UrlGeneratorInterface $urlGenerator) {}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse|JsonResponse
{
if ($request->getContentTypeFormat() === 'json' || $request->isXmlHttpRequest()) {
return new JsonResponse([
'status' => 'error',
'errorMessage' => $exception->getMessageKey(),
], Response::HTTP_UNAUTHORIZED);
}
// Store the error in the session
$request->getSession()->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse($this->urlGenerator->generate('app_login'));
}
}
Since Passkeys often bypass the traditional login form, you need to define where the user goes after a successful “Handshake.”
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
readonly class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function __construct(private UrlGeneratorInterface $urlGenerator) {}
public function onAuthenticationSuccess(Request $request, TokenInterface $token): RedirectResponse
{
return new RedirectResponse($this->urlGenerator->generate('app_dashboard'));
}
}
Transitioning to Apple Passkeys with Symfony 7.4 isn’t just a security upgrade; it’s a significant improvement to your user experience. By removing the friction of password managers, “forgot password” emails, and complex character requirements, you increase conversion and user retention.
\ As a senior developer or lead, your priority is ensuring that this implementation remains maintainable. By sticking to the WebAuthn-Symfony-Bundle and PHP 8.x attributes, you ensure that your codebase remains idiomatic and ready for future Symfony LTS releases.
\ Source Code: You can find the full implementation and follow the project’s progress on GitHub: [https://github.com/mattleads/PasskeysAuth]
If you found this helpful or have questions about the implementation, I’d love to hear from you. Let’s stay in touch and keep the conversation going across these platforms:
\
2026-03-13 23:39:10
Some ads sell products. The rare ones change culture. The 20th century gave us both kinds, and the gap between them is everything. Before social media, algorithmic targeting, and A/B testing, the campaigns that broke through did so with nothing but a sharp idea, honest copy, and a deep understanding of what people actually wanted to feel.
In 1999, Ad Age ranked the 100 greatest campaigns of the century. What's striking about that list isn't how dated it looks. It's how relevant it still is. The principles behind the best work haven't changed. Only the platforms have.
Here are 7 campaigns from that era that every marketer should know, why they worked, and what they can still teach you right now.
Agency: Doyle Dane Bernbach · Ad Age ranking: #1
There is no campaign more studied, more referenced, or more deserving of the top spot. In 1959, Volkswagen needed to sell a small, odd-looking German car to an American market obsessed with size, chrome, and Cadillacs. In post-war America, that was a nearly impossible brief. And not just commercially, but culturally.
Doyle Dane Bernbach (DDB) did the opposite of everything the industry expected. Art director Helmut Krone placed a tiny photo of the Beetle in the upper left corner of a full page, surrounded by nothing but white space. Copywriter Julian Koenig wrote two words underneath: Think small.
Ad Age described DDB as having given "advertising permission to surprise, to defy and to engage the consumer without bludgeoning him." The campaign worked. According to Ad Age's historical record, DDB created six of the greatest 100 campaigns of the century, and Think Small was the crown jewel. Annual U.S. Beetle sales climbed from 120,000 units in 1959 to over 423,000 by 1968.
Why it's timeless: Truth delivered with confidence is more persuasive than any exaggerated claim. In a feed full of loud creatives and inflated promises, this lesson is more relevant now than ever.
Agency: D'Arcy Co. · Ad Age ranking: #2
Long before "emotional branding" had a name, Coca-Cola was already doing it. "The pause that refreshes" wasn't selling a drink; it was selling a moment. A feeling. A permission to stop, breathe, and enjoy something small.
The slogan was developed by Archie Lee at D'Arcy Advertising, working closely with Coca-Cola president Robert Woodruff, who — according to the Coca-Cola Company's own historical archive — believed the role of advertising was "making people like you," not selling a product. That philosophy shaped everything. Launched in 1929, as the Coca-Cola Company's advertising history confirms, the slogan became the anchor of the brand's identity through the Depression era, positioning Coca-Cola as a democratic, affordable pleasure available to everyone at any moment of the day.
According to the Coca-Cola Company's historical archive, Woodruff and Lee also commissioned artist Haddon Sundblom in 1931 to paint the now-iconic red-suited Santa Claus for Coca-Cola ads, further cementing the brand's connection to warmth, shared moments, and human feeling. Everything that followed built on this emotional foundation.
Why it's timeless: People don't buy products, they buy feelings. Coca-Cola figured that out in 1929. Every brand building emotional campaigns today is working from the same playbook.
Agency: Leo Burnett Co. · Ad Age ranking: #3
Marlboro was originally marketed as a women's cigarette, sold under the genteel tagline "Mild as May." Philip Morris hired Leo Burnett in November 1954 to fix that, and what Burnett created was one of the most dramatic brand repositionings in advertising history.
According to Ad Age's encyclopedia entry on Leo Burnett, the agency "took a personal role in repositioning the brand from a women's cigarette to a men's with the introduction of the Marlboro Man campaign." The first ads featured cowboys. As documented in Ad Age's tobacco marketing archive, Burnett told Philip Morris:
"The cowboy is an almost universal symbol of admired masculinity."
Research had found that smokers considered filter cigarettes "slightly effeminate," so every element was designed to counteract that perception. By 1962, Philip Morris settled on the cowboy as the exclusive Marlboro image; by 1963, he had a home: Marlboro Country.
In 1999, Ad Age named the Marlboro Man campaign the third most important of the century and the cowboy the top advertising icon of the century, one of four icons created by Leo Burnett to make the list. As Ad Age noted, no other single agency had more than one. Philip Morris itself later called Marlboro "the No. 1 trademark in the world."
Why it's timeless: People buy who they want to become, not what a product does. The Marlboro Man sold an identity so completely that it transcended the product itself.
Agency: Wieden+Kennedy · Ad Age ranking: #4
In 1988, Nike was losing ground to Reebok, which had dominated the aerobics boom of the mid-80s. Wieden+Kennedy co-founder Dan Wieden needed a single line to unify a series of very different TV spots, and he wrote it the night before the client presentation.
The origin is surprising. As reported by NPR in their tribute following Wieden's death in 2022, the phrase was inspired by the last words of convicted murderer Gary Gilmore before his execution: "Let's do it." Wieden changed two words and stripped it of its darkness. The first ad to carry the line featured an 80-year-old man named Walt Stack jogging across the Golden Gate Bridge — not a superstar athlete, just a person doing it. That deliberate inclusivity made the campaign speak to everyone.
As NPR documented, Nike grew its worldwide sales from $877 million in 1988 to $9.2 billion by 1998. Its share of the North American sport-shoe market climbed from 18% to 43% over the same decade.
"For some reason that line resonated deeply in the athletic community and just as deeply with people who had little or no connection to sports."
Wieden said of the response. More than 35 years later, "Just Do It" is still running.
Why it's timeless: The best slogans aren't about the product, they're about the person using it. "Just Do It" is a philosophy, not a tagline, and it speaks to something universal in human ambition.
Agency: N.W. Ayer & Son · Ad Age ranking: #6
Few campaigns have shaped human behaviour as profoundly as this one. Before De Beers, diamond engagement rings were not a cultural norm. Fewer than 20% of American brides owned one by the end of the 1930s, as documented in Ad Age's encyclopedia entry on De Beers.
In 1948, before a major agency presentation, N.W. Ayer copywriter Frances Gerety scribbled the line "A diamond is forever." As Ad Age reported in its De Beers encyclopedia entry, the slogan "captured both the durability of the stone and the romantic aspirations of couples entering into marriage" and immediately became the mainstay of De Beers' U.S. campaign. At the same time, N.W. Ayer developed the "Four Cs" of diamond buying — cut, color, clarity, and carat weight — framing the entire purchase category in De Beers' own language.
The numbers are staggering. By the end of the 1940s, the share of married U.S. women who owned diamond engagement rings had risen to 60%, according to Ad Age. By the 1980s it surpassed 70%. When De Beers took the campaign to Japan in 1968, a market where fewer than 5% of women received diamond engagement rings at the time, that figure reached 60% by 1981, as documented by Ad Age. De Beers was spending $200 million a year in advertising across 34 countries at peak. Ad Age later voted "A Diamond Is Forever" the most iconic advertising slogan of the 20th century.
Why it's timeless: It's the ultimate proof that advertising can create cultural norms, not just reflect them. De Beers didn't sell diamonds, they made diamonds feel necessary.
Agency: Doyle Dane Bernbach · Ad Age ranking: #10
DDB appears twice on this list because they earned it twice. "We Try Harder" was born from a brief that would have scared off almost any other agency.
In 1962, Avis had only an 11% market share and had not turned a profit in 13 years, according to Campaign magazine's historical account of the campaign. New CEO Robert Townsend called in DDB's Bill Bernbach, who demanded 90 days to learn the company before writing a word. During that deep-dive, when DDB asked whether Avis had newer cars, more locations, or lower rates than Hertz, the answer to every question was no. "Well," said Townsend, "we do try harder." That honest admission became the brief.
Copywriter Paula Green, whom Campaign described as having gone "completely against the prevailing Madison Avenue philosophy that ads must never acknowledge a brand weakness," turned it into "When you're only No. 2, you try harder. Or else." David Ogilvy later praised the campaign as "diabolical positioning," as recorded in Slate's 2013 investigation of the Hertz-Avis rivalry. Fred Danzig, then an Ad Age reporter, captured the industry reaction when the campaign launched:
"The audacity, the originality, the freshness, the life, the sassy spirit… it forever changed the way Madison Avenue communicated to the world."
The results were immediate. Within a year, Avis turned a $3.2 million loss into a $1.2 million profit, its first in over a decade, as confirmed by both Campaign and Slate. Hertz executives, Slate reported, projected that by 1968, Avis might need a new campaign because it would no longer be No. 2.
Why it's timeless: Honesty about your weaknesses, delivered with confidence, builds more trust than hollow claims of superiority. In a world of inflated promises, admitting what you're not is a surprisingly powerful differentiator.
Agency: Chiat/Day · Ad Age ranking: #12
It aired once, during Super Bowl XVIII on January 22, 1984. It never ran on national television again. And it remains the most discussed commercial in advertising history.
Directed by Ridley Scott, the ad depicted a grey, dystopian world of conformity being shattered by a lone woman hurling a sledgehammer through a screen showing "Big Brother." According to Ad Age's archive of the creatives behind the spot, it was written by Steve Hayden, art directed by Brent Thomas, and creative directed by Lee Clow, with Ridley Scott brought in while he was in London on "Blade Runner." As Clow told Ad Age:
"Steve Jobs' simple challenge was, 'I think Macintosh is the greatest product in the history of the world. Make an ad that tells them that.'"
Apple used the commercial to position the Mac as the antidote to IBM's domination of the information age, two days before the computer's launch.
What makes the story richer is how close it came to never airing. Apple's board of directors hated the spot and tried to kill it. Chiat/Day executive Jay Chiat held onto the Super Bowl airtime regardless. The ad ran once and generated massive earned media far beyond anything a single TV buy could have produced. Ad Age named "1984" the Commercial of the Decade for the 1980s. As Ad Age's profile of Ridley Scott noted, the spot "effectively turned the Super Bowl into a platform for mini-blockbuster entertainment", a legacy that defines Super Bowl advertising strategy to this day.
Why it's timeless: The ad barely showed the product. It told a story about who Apple customers were — rebels, individuals, people who think differently. Forty years later, Apple still builds campaigns around that same identity.
Looking across these campaigns, the patterns are impossible to miss.
None of them led with features. Not one. They led with feelings, identities, moments, and ideas. They treated their audiences as intelligent people capable of being moved, not consumers to be pushed.
They were also all built on a single, clear idea, one thought, executed with total conviction. And most importantly, they were honest. Sometimes uncomfortably so. Avis admitted they were second. De Beers built an entire campaign on a stone's one real attribute: it doesn't break. That kind of radical honesty in advertising is still rare, and still extraordinarily effective when you have the nerve to try it.
In 2026, with AI creative tools, performance dashboards, and algorithmic targeting dominating how we think about advertising, it's easy to forget that the fundamentals haven't changed. The best campaigns still earn attention rather than buy it. They still build something people want to belong to. They still tell one true thing in a way that makes people feel seen.
That's what made these campaigns timeless. And that's what will make the next great campaign timeless, too.
\
2026-03-13 23:00:58
AI assistants don’t have “bad memory.” They have bad governance.
You’ve seen it:
el-input.” It drops <el-input v-model="value" /> like it’s doing you a favor.This isn’t about intelligence. It’s about incentives.
In Claude Code, Skills are available context, not a hard constraint. If Claude “feels” it can answer without calling a Skill, it will. And your project handbook becomes decoration.
On our team, building a RuoYi-Plus codebase with Claude Code, we tracked it:
Without intervention, Claude proactively activated project Skills only ~25% of the time.
So 3 out of 4 times, your “rules” aren’t rules. They’re a suggestion.
We wanted something stricter: a mechanism that makes Claude behave less like a clever intern and more like a staff engineer who reads the playbook before writing code.
The fix wasn’t more prompting.
The fix was Hooks.
After ~1 month of iteration, we shipped a .claude configuration stack:
Result:
Skill activation for dev tasks: ~25% → 90%+ Less rule-violating code, fewer “please redo it” cycles, and far fewer risky tool actions.
This article breaks down the architecture so you can reproduce it in your own repo.
Claude Code’s default flow looks like this:
User prompt → Claude answers (maybe calls a Skill, maybe not)
That “maybe” is the problem.
Claude’s internal decision heuristic is usually:
So the system drifts toward convenience.
What you want is institutional friction: a lightweight “control plane” that runs before Claude starts reasoning, and shapes the work every time.
We implemented a hook that fires at the earliest moment: UserPromptSubmit.
It prints a short policy block that Claude sees before doing anything else:
We keep it deliberately dumb and deterministic:
// .claude/hooks/skill-forced-eval.js (core idea, simplified)
const prompt = process.env.CLAUDE_USER_PROMPT ?? "";
// Escape hatch: if user invoked a slash command, skip forced eval
const isSlash = /^\/[^\s/]+/.test(prompt.trim());
if (isSlash) process.exit(0);
const skills = [
"crud-development",
"api-development",
"database-ops",
"ui-pc",
"ui-mobile",
// ... keep going (we have 26)
];
const instructions = [
"## Mandatory Skill Activation Protocol (MUST FOLLOW)",
"",
"### Step 1 — Evaluate",
"For EACH skill, output: [skill] — Yes/No — Reason",
"",
"### Step 2 — Activate",
"If ANY skill is Yes → call Skill(<name>) immediately.",
"If ALL are No → state 'No skills needed' and continue.",
"",
"### Step 3 — Implement",
"Only after Step 2 is done, start the actual solution.",
"",
"Available skills:",
...skills.map(s => `- ${s}`)
].join("\n");
console.log(instructions);
Before (no hook):
“Build coupon management.” Claude starts coding… and ignores your 4-layer architecture or banned components.
After (forced eval hook):
Claude must first produce an explicit decision table, then activate Skills, then implement.
The behavioral shift is dramatic because you’re eliminating “optional compliance.”
Because we intentionally added a fast path.
When a user knows what they want, typing a command like:
/dev build coupon management/crud b_coupon/checkshould be instant. So the hook skips evaluation for slash commands and lets the command workflow take over.
That’s the tradeoff:
Think of hooks as a CI pipeline for an agent session—except it runs live, in your terminal.
We use four key points:
When a session starts, we show:
Example output:
🚀 Session started: RuoYi-Plus-Uniapp
Time: 2026-02-16 21:14
Branch: master
⚠️ Uncommitted changes: 5 files
📋 TODO: 3 open / 12 done
Shortcuts:
/dev build feature
/crud generate module
/check verify conventions
Why it matters: Claude stops acting like it’s entering a blank room.
This is the “must read the handbook” gate.
Claude Code is powerful because it can run tools: Bash, write files, edit code.
That’s also how accidents happen.
PreToolUse is your last line of defense before something irreversible.
We block a small blacklist (and warn on a broader greylist):
// .claude/hooks/pre-tool-use.js (conceptual)
const cmd = process.env.CLAUDE_TOOL_INPUT ?? "";
const hardBlock = [
/rm\s+(-rf|--recursive).*\s+\//i,
/drop\s+(database|table)\b/i,
/>\s*\/dev\/sd[a-z]\b/i,
];
if (hardBlock.some(p => p.test(cmd))) {
console.log(JSON.stringify({
decision: "block",
reason: "Dangerous command pattern detected"
}));
process.exit(0);
}
// Optionally: warn/confirm on sensitive actions (mass deletes, chmod -R, etc.)
This isn’t paranoia. We’ve seen models “clean temp files” with rm -rf in the wrong directory. You want a guardrail that doesn’t rely on the model being careful.
When Claude finishes, we:
Example:
✅ Done — 8 files changed
Next steps:
- @code-reviewer for backend conventions
- SQL changed: sync migration scripts
- Consider: git commit -m "feat: coupon module"
The goal: eliminate the “it worked in the chat” gap.
Once activation is deterministic, Skills become what they were supposed to be: a domain-specific knowledge base.
We built 26 Skills across:
Every SKILL.md follows the same skeleton:
# Skill Name
## When to trigger
- Keywords:
- Scenarios:
## Core rules
### Rule 1
Explanation + example
### Rule 2
Explanation + example
## Forbidden
- ❌ ...
## Reference code
- path/to/file
## Checklist
- [ ] ...
This consistency matters because the model learns how to consume Skills.
Skills solve “what is correct.”
Commands solve “what is the process.”
/dev: a 7-step development pipelineWe designed /dev as an opinionated workflow:
It’s basically: “how seniors want juniors to work” encoded as a runnable script.
/crud: generate a full module from a tableInput:
/crud b_coupon
Output (example set):
Manual effort: 2–4 hours Command-driven: 5–10 minutes (plus review)
/check: full-stack convention linting (human-readable)This is where we turn Skills into a verifier:
Some tasks should be handled by a dedicated subagent:
@code-reviewer: convention checks with a strict checklist@project-manager: update status docs, TODOs, progress metricsThe advantage isn’t “more intelligence.” It’s separation of concerns and reduced context pollution in the main session.
A practical pattern:
@code-reviewer
This is the architecture in one sentence:
Hooks enforce behavior, Skills provide standards, Commands encode workflows, Agents handle parallel expertise.
And yes—this is how you turn a “general AI assistant” into a “repo-native teammate.”
If you want the smallest version that still works, build this:
.claude/
settings.json
hooks/
skill-forced-eval.js
pre-tool-use.js
skills/
crud-development/
SKILL.md
settings.json (UserPromptSubmit hook){
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/skill-forced-eval.js"
}
]
}
],
"PreToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/pre-tool-use.js"
}
]
}
]
}
}
Then iterate:
Your model is already capable.
What’s missing is a system that makes the right behavior automatic.
A smart new hire without a handbook will freestyle. A smart new hire with:
…becomes consistent fast.
Claude is the same.
\