
De Nederlandse politie heeft een nepwebshop opgezet om burgers bewust te maken van de gevaren van ticketfraude. Dat las ik bij Tweakers. De site doet zich voor als ticketplatform waar mensen “snel en eenvoudig toegang [kunnen] krijgen tot uitverkochte evenementen”. Maar wie een ticket wil kopen, krijgt meteen “Ai… je trapte er (bijna) in” te horen. Diverse mensen vroegen me of dat wel mag.
De site was een behoorlijk succes, zo te lezen:
Meer dan 300.000 keer zagen mensen tussen 30 oktober 2025 en 11 januari 2026 een advertentie van TicketBewust.nl langskomen op Marktplaats. Ruim 30.000 mensen klikten erop en bekeken de advertentie. 7402 keer werd er doorgeklikt naar de nepwebshop. 3432 keer probeerden mensen, ondanks de ingebouwde aanwijzingen dat het om een nepwebshop gaat, ook echt een kaartje te bestellen.Het is niet de eerste keer dat men zoiets doet. Vrijwel exact een jaar geleden werd onthuld dat de electronicadozenschuiver Pakjedealsnu een politiesite was. En in alweer 2013 hadden we de Taskforce Opsporing Vuurwerkbommenmakers die pretendeerde illegaal vuurwerk te verkopen.
De site zit vol met aanwijzingen, zoals dat je alleen “uitverkochte” tickets kunt kopen en het vestigingsadres dat bij een politiebureau uitkomt. En de stagiair mocht helemaal los bij Trustpilot om 1-sterrecensies namens “Ben de Klos” en anderen te componeren.
Weinig mis mee, zou ik zeggen. Het past keurig binnen de algemene voorlichtende en helpende taak van de politie. Omdat er niet werkelijk transacties worden afgesloten, hoeven we ook geen discussie over misleiding of oplichting te voeren. Tevens wordt zo keurig de AVG omzeild: je kunt niet eens persoonsgegevens invullen om te bestellen.
Arnoud
Het bericht Politie waarschuwt via eigen nepwebshop voor ticketfraude op Marktplaats, mag dat? verscheen eerst op Ius Mentis.
DAVOS (ANP/BLOOMBERG) - Kunstmatige intelligentie zal "grootschalige immigratie" om de economie en arbeidsmarkten van westerse landen te ondersteunen overbodig maken. Dat heeft topman Alex Karp van het Amerikaanse technologiebedrijf Palantir gezegd op het World Economic Forum (WEF) in het Zwitserse Davos. "Er zullen meer dan genoeg banen zijn voor de burgers van uw land, vooral voor mensen met een beroepsopleiding."
Palantir ontwikkelt software voor het analyseren en koppelen van grote hoeveelheden data. Die technologie wordt niet alleen gebruikt door bedrijven, maar ook door militaire en veiligheidsdiensten, waaronder de Amerikaanse immigratiedienst ICE.
Daarnaast ontwikkelt het bedrijf software om bedrijfsprocessen te analyseren en te herstructureren. Palantir verwacht dat AI-software steeds vaker taken zal overnemen, vooral die van kantoormedewerkers. Sommige bedrijven, zoals Amazon, hebben met het oog hierop al veel werknemers ontslagen.
TEL AVIV (ANP/RTR) - De Israëlische premier Benjamin Netanyahu heeft een uitnodiging geaccepteerd om deel te nemen aan de Raad van Vrede. Deze raad moet toezicht houden op het toekomstige Palestijnse overgangsbestuur in de Gazastrook.
De raad is een idee van de Amerikaanse president Donald Trump voor het stoppen van de oorlog in Gaza, maar hij wil de raad ook gebruiken voor andere conflicten in de wereld. Eerder werd al bekend dat hij uitnodigingen had verstuurd, waaronder naar Israël. Het was toen nog niet duidelijk of Netanyahu die zou accepteren.
We hebben de demente oranje clown die de wereld bedreigt, de aarde wordt steeds warmer en gevaarlijker. Maar het kan erger dan 2026.
Voor wie denkt dat onze tijd uitzonderlijk somber is: middeleeuwse historici wijzen één jaar aan dat nog veel duisterder was. In 536 na Christus begon volgens Harvard‑archeoloog Michael McCormick “een van de slechtste periodes om in leven te zijn, zo niet het slechtste jaar”.[ science]
In kronieken uit Europa, het Midden-Oosten en Azië duikt vanaf dat jaar een mysterieuze “mist” op, die de zon maandenlang verduisterde en de temperatuur met tot wel 2,5 graden liet kelderen – de koudste decade in 2300 jaar, reconstrueren ijsboringen en boomringen. Zomers bleven grijs, oogsten mislukten en Ierse annalen spreken over een “mislukking van het brood” tussen 536 en 539.[
Die plotselinge klimaatschok is inmiddels teruggevoerd op enorme vulkanische uitbarstingen in 536 en 540, die een wereldwijde stofsluier in de stratosfeer achterlieten. Bomen in Siberië vertonen een ongekende krimp in jaarringen, een signaal van dramatische groeistress en misoogsten zonder precedent in tweeduizend jaar.
Alsof dat niet genoeg was, volgde in 541 de Justiniaanse pest, een vroege builenpest-pandemie die mogelijk een derde van de bevolking in delen van het oostelijke Romeinse Rijk wegvaagde. Economieën stortten in, steden liepen leeg en het laat-Romeinse rijk kwam in een versnelde neergang terecht. Tegen die achtergrond oogt zelfs onze tijd van polarisatie, pandemieën en oorlogen minder uniek dan vaak wordt gedacht.
ZÜRICH (ANP) - Voormalig Unilever-topman Hein Schumacher gaat leiding geven aan de grote chocoladeproducent Barry Callebaut. Hij neemt op 26 januari de hoogste functie bij het in Zwitserland gevestigde bedrijf over van vertrekkend topman Peter Feld.
Feld gaat volgens 's werelds grootste chocoladeproducent andere carrièremogelijkheden nastreven. Schumacher was tussen 2023 en 2025 topman van Unilever. Hij werkte eerder in topfuncties voor zuivelbedrijf FrieslandCampina.
In een verklaring stelt Schumacher "vereerd" te zijn om als bestuursvoorzitter aan de slag te gaan bij Barry Callebaut. Volgens hem heeft het bedrijf een periode van "ongekende marktturbulentie" achter de rug. Zo zijn de prijzen van cacao de afgelopen jaren sterk gestegen door slechte oogsten in West-Afrika. Daarnaast staat de verkoop van chocolade door de stijgende prijzen onder druk.
SEOUL (ANP/RTR) - De Zuid-Koreaanse oud-premier Han Duck-soo heeft 23 jaar gevangenisstraf gekregen in een zaak rond de militaire noodtoestand van 2024. Een rechtbank acht de 76-jarige politicus schuldig aan onder meer het deelnemen aan een cruciale handeling van opstand.
President Yoon Suk-yeol kondigde de noodtoestand aan in december 2024 en trok deze uren later weer in onder dwang van het parlement. De rechter stelt dat de toenmalige premier Han heeft gefaald een kabinetsvergadering te houden, wat volgens de Zuid-Koreaanse regels noodzakelijk is voor het instellen van de noodtoestand.
Zowel Yoon als Han werd in de nasleep van de noodtoestand afgezet door het parlement. Maar Hans afzetting werd teruggedraaid door het Grondwettelijk Hof. De premier, die ook enige tijd als waarnemend president diende, stapte uiteindelijk zelf op om mee te doen aan de vervroegde presidentsverkiezingen, maar trok zich later terug uit de verkiezingsstrijd.
Bork!Bork!Bork! Sometimes technology is made of sterner stuff than we give credit for, such as this ATM, which has clung on to life – and power – despite the indignities heaped upon it.…
De discussie over betaald parkeren en vergunningen wordt meestal gevoerd in termen van leefbaarheid en schaarse openbare ruimte. Dat zijn verdedigbare uitgangspunten. In veel grote steden bestaat daarom een regeling die doorgaans POET heet: Parkeren Op Eigen Terrein. Die regeling is ingevoerd om misbruik van goedkope bewonersvergunningen tegen te gaan, bijvoorbeeld door commerciële voertuigen of door huishoudens die feitelijk al over een privéparkeerplek beschikken.
Wat op papier een redelijke parkeermaatregel lijkt, krijgt in de praktijk regelmatig het karakter van een inkomensfilter. Dat wordt zichtbaar in Kortenbos, een Haagse wijk waar parkeerbeleid steeds minder draait om ruimte en steeds meer om betaalbaarheid. Hier zie je wat er gebeurt wanneer beleidsdoelen worden doorvertaald zonder rekening te houden met de sociale realiteit. Kortenbos kent relatief veel sociale huur en bewoners met beperkte financiële marges.
De POET-regeling was nadrukkelijk niet bedoeld om mensen die sociaal huren hun auto af te nemen of om mobiliteit afhankelijk te maken van inkomen. Toch is dat precies wat hier dreigt te gebeuren. Sociale huurders die eerder voor minder dan honderd euro per jaar een parkeervergunning hadden, verliezen die vergunning onder POET. De reden is dat hun woning administratief gekoppeld is aan een nabijgelegen parkeergarage. Daarmee vallen zij buiten het regime van betaalbaar bewonersparkeren en worden zij geconfronteerd met commerciële tarieven. Die worden als ‘schappelijk’ gepresenteerd, terwijl zij in de praktijk oplopen tot ongeveer duizend euro per jaar, tien keer meer dan daarvoor.
Voor huishoudens met voldoende financiële ruimte vormt dat een vervelende extra kostenpost. Voor mensen met minder draagvlak is het doorslaggevend. Parkeerbeleid fungeert hier niet als prikkel, maar als financiële drempel.
Voor veel bewoners in Kortenbos is een auto geen luxe. Werk buiten de buurt, onregelmatige diensten en mantelzorg laten zich vaak slecht combineren met openbaar vervoer. Door de kosten van parkeren zo abrupt en fors te verhogen, verdwijnt mobiliteit als reële optie voor een specifieke groep bewoners. Dat is geen gedragssturing, maar uitsluiting via de portemonnee.
Daar komt bij dat bezwaar maken in Den Haag voor veel mensen in de praktijk vrijwel ondoenlijk is. De gemeente laat bewoners zelf uitzoeken waar en hoe bezwaar moet worden ingediend en reageert pas weken later met een standaard afwijzing, voorzien van een verwijzing naar dezelfde procedure die net is doorlopen. Daarbij wordt vermeld dat uitzonderingen slechts onder strikte voorwaarden mogelijk zijn en zelden worden toegekend, zonder toe te lichten wat die voorwaarden zijn. Door de late reactie wordt het vrijwel onmogelijk om het traject af te ronden voordat het nieuwe beleid ingaat. De communicatie is een cirkelredenering die bewoners van het kastje naar de muur stuurt, met als impliciete boodschap: zoek het zelf maar uit.
Formeel zal het beleid sluitend zijn en correct worden toegepast. Inhoudelijk schuurt het. POET verandert hier van een maatregel tegen misbruik in een instrument dat via prijsdruk bepaalt wie mobiel mag blijven. Niet op basis van noodzaak, maar op basis van draagkracht.
Juist omdat de POET-regeling daar niet voor bedoeld is, is deze uitwerking problematisch. Een beleid dat mobiliteit voor lagere inkomens onbetaalbaar maakt en tegelijk de bezwaarprocedure zo ondoorzichtig inricht, kan moeilijk neutraal worden genoemd, hoe technisch het ook wordt gepresenteerd.
Zolang de gemeente vasthoudt aan formele logica en wegkijkt van de financiële impact, blijft zij spreken over goede intenties terwijl de praktijk iets anders laat zien.
Grace was tarcking down some production failures, which put her on the path to inspecting a lot of URLs in requests. And that put her onto this blob of code:
app.get(
(
[
"/api/ddm/getProjectList",
":dueDate",
":status",
":userAssignedId",
":managerID",
":clientID",
":projectHeaderID",
":tagId",
":companyId",
":clientGroup",
":isDefault",
":dateRange",
":dateToFilter",
":tagIds",
":statusIds",
":repeatValues",
":engagementID?",
":completionDate?"
]
.join( "/" )
),
ddmDboardCtrl.getProjectList
);
This defines a route in ExpressJS for handling GET requests. And it defines the route such that every single parameter on the request is contained in the path portion of the URL. That raises questions about why you need seventeen parameters to fulfill your request and what that means for our API design, but it's even worse than it looks: most of those parameters are allowed to be null.
That means the request looks like this:
GET /api/ddm/getProjectList/null/null/null/null/878778/null/null/2049/null/null/null/null/null/null/null/3532061?
For bonus point, the developer responsible for that awful API also has a "special" way for dealing with possibly empty returns:
(
fs.readdirSync( `${ GLOBAL_DIRECTORY_PATH }` )
||
(
[ ]
)
)
.map(
(
function( moduleName ){
return (
path.resolve(
( `${ GLOBAL_DIRECTORY_PATH }/${ moduleName }` )
)
);
}
)
)
This code calls reddirSync and in case that returns null, ||s the result with an empty array. Only one problem: readdirSync never returns null. It returns an empty array when there are no results.
Also, this indenting is as submitted, which… what is even happening?
This developer has a strange relationship with nulls- defending against them when they're not a risk, spamming them into URLs. They have gazed too long into the null, and the null gazes back into them.
Grace sends us, in her words, "the function that validates the data from the signup form for a cursed application."
It's more than one function, but there are certainly some clearly cursed aspects of the whole thing.
function trimStr(v) {
return typeof v === "string" ? v.trim() : v;
}
This function, itself, isn't cursed, but it certainly represents a bad omen. Take any type of input, and if that input happens to be a string, return the trimmed version. Otherwise, return the input unchanged. I've got good news and bad news about this omen: the good news is that it isn't used in most of the code that follows, and the bad news is that it is used in some of the code that follows.
The next function builds a validation schema using the yup library, and we'll take this one in chunks, since it's long.
function buildSchema() {
// Common string with trim transform
const t = () =>
yup
.string()
.transform((val) => (typeof val === "string" ? val.trim() : val))
.nullable();
See, I promised that the trimStr function wasn't used in most of the code- because they just copy/pasted its body where they needed it.
let emailField = yup
.string()
.transform((val) => (typeof val === "string" ? val.trim().toLowerCase() : val))
.nullable()
.required("email is required");
emailField = emailField.test("email-format", "email is invalid", (v) => {
if (!v) return false; // required above
// Simple email format validation
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(v);
});
I assume t above is meant to be a common base transformation, so you don't have to constantly rewrite the trim functionality. Though this isn't precisely a trim- it also canonicalizes the address to lower case. That will likely work most of the time, but while the domain portion of an email address is case insensitive, the address part of it is not- Remy@somesite.com and remy@somesite.com could be different addresses.
They also make the email field both nullable and required, which is an interesting choice. Not one they're confident about, as they also check that the required field is actually populated in their test function. Then they do a regex to validate the email address, which it's worth noting that email addresses shouldn't be validated by regexes, but also yup already includes an email validation, so none of this is necessary.
let passwordField = yup.string().nullable().required("password is required");
passwordField = passwordField
.test(
"password-min-length",
"password must be at least 8 characters",
(v) => !!v && v.length >= 8
)
.test(
"password-alpha-num",
"password must contain letters and numbers",
(v) => !!v && (/[A-Za-z]/.test(v) && /\d/.test(v))
);
let confirmPasswordField = yup.string().nullable().required("confirmPassword is required");
confirmPasswordField = confirmPasswordField.test(
"passwords-match",
"password and confirmPassword do not match",
function (v) {
const pwd = this.parent.password;
if (!v && !pwd) return false; // both empty => invalid
if (!v || !pwd) return true; // required rules will handle
return v === pwd;
}
);
Passwords limited to alphanumeric is a choice. A bad one, certainly. Again we also see the pattern of nullable required fields.
let telephoneField = t().required("telephone is required");
telephoneField = telephoneField.test("telephone-digits", "telephone is invalid", (v) => {
if (!v) return false;
const digits = (v.match(/\d/g) || []).length;
return digits >= 7;
});
Oh, at least on phone numbers they use that common base transformation. Again, they're not using the built-in features of yum which can already validate phone numbers, but hey, at least they're making sure that there are at least seven digits, which probably works in some places. Not everywhere, but some places.
const schema = yup.object().shape({
firstName: t().required("firstName is required").max(100, "firstName too long"),
lastName: t().required("lastName is required").max(100, "lastName too long"),
companyName: t().required("companyName is required").max(150, "companyName too long"),
telephone: telephoneField,
email: emailField,
product: t().max(150, "product too long"),
password: passwordField,
confirmPassword: confirmPasswordField,
affiliateId: t(),
visitorId: t(),
});
return schema;
}
And here we finish constructing the schema, and look at that- we do use that base transformation a few more times here.
How do we use it?
function validateSignupPayload(payload = {}) {
// Normalize input keys to match schema: support email/emailAddress and telephone/phoneNumber
const normalized = {
firstName: trimStr(payload.firstName),
lastName: trimStr(payload.lastName),
companyName: trimStr(payload.companyName),
telephone: trimStr(payload.telephone) || trimStr(payload.phoneNumber),
email: (trimStr(payload.email) || trimStr(payload.emailAddress) || "").toLowerCase(),
product: trimStr(payload.product),
password: typeof payload.password === "string" ? payload.password : payload.password || undefined,
confirmPassword:
typeof payload.confirmPassword === "string" ? payload.confirmPassword : payload.confirmPassword || undefined,
affiliateId: trimStr(payload.affiliateId),
visitorId: trimStr(payload.visitorId),
};
const schema = buildSchema();
try {
const cleaned = schema.validateSync(normalized, { abortEarly: false, stripUnknown: true });
return { errors: [], cleaned };
} catch (e) {
const errors = Array.isArray(e.errors) ? e.errors : ["Invalid arguments"];
// Still return partial cleaned data from normalization
return { errors, cleaned: normalized };
}
}
Here, we "normalize" the inputs, which repeats most of the logic of how we validate the inputs. Mostly. This does have the added benefit of ensuring that the password fields could be undefined, which is not null. More fun, to my mind, is that the input form is clearly inconsistent about the naming of fields- is it telephone or phoneNumber? email or emailAddress?
I agree that this is cursed, less in the creeping dread sense, and more in the "WTF" sense.
Marjorie Baylis has added a photo to the pool: