{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"vulnerabilities-that-arrive-by-default","title":"Insecure defaults, inherited","url":"https://addaly.com/learn/how-software-changed/vulnerabilities-that-arrive-by-default","position":70,"of":87,"minutes":10,"answer":"Generated code inherits the insecure defaults of old public code, and the one class that no scanner can catch is missing authorisation.","keyIdea":"Generated code inherits the insecure defaults of old public code, and the one class that no scanner can catch is missing authorisation, because who may touch which object is a business rule that exists in nobody's training corpus."},"body":{"format":"markdown","text":"## Where the insecurity comes from\n\nA generator produces what is typical in its training data. Its training data is public code. Public code is, on average, old, written under deadline, copied from an answer site, and never security-reviewed. The output inherits that distribution, and the inheritance is mechanical rather than malicious.\n\nSo the defects are not exotic. They are the ordinary ones, arriving at the rate at which you generate.\n\n## What actually turns up\n\n**String-built SQL.** Parameterised queries are common in good code and concatenation is common in tutorial code. Both are in the corpus.\n\n**Old cryptography.** MD5 or SHA-1 for password storage, because a decade of examples did that. ECB mode. A hard-coded initialisation vector. `Math.random()` or an unseeded PRNG for a session token or a password reset link, which is a full account takeover rather than a subtle weakness.\n\n**Verification switched off.** `verify=False` on a Python request, `rejectUnauthorized: false` in Node, `InsecureSkipVerify: true` in Go. These appear constantly in public code because they are how people get past a certificate error while debugging, and the debugging line ships.\n\n**Deserialisation of untrusted input.** `pickle.loads` on a request body is remote code execution, stated plainly.\n\n**Permissive by default.** CORS set to `*`, an S3 bucket policy wider than needed, a database user with more rights than the application uses, an XML parser with external entities enabled.\n\n## The class no tool finds\n\nOne category is different in kind, and it is the most common serious finding in real applications: **missing authorisation**.\n\nAsk for an endpoint that returns an invoice and you will very likely get authentication (is this a logged-in user) and very likely not get authorisation: is this *their* invoice.\n\n```python\n@app.get(\"/invoices/{invoice_id}\")\ndef get_invoice(invoice_id: int, user = Depends(current_user)):\n    return db.query(Invoice).get(invoice_id)   # whose invoice?\n```\n\nThat code is correct-looking, passes its tests, and lets any logged-in customer read every invoice in the system by changing a number in the URL. It is the top entry in the OWASP application security risks list for a reason.\n\nThe mechanism is worth being precise about. The model cannot know this is wrong, because the rule it violates is not in the code. It lives in a sentence somebody said in a meeting: *a customer can see their own invoices and an administrator can see all of them, except finance staff, who can see any invoice but cannot see the customer's address.* Nothing in any corpus contains that. It is not a knowledge gap; it is an information that does not exist outside your organisation.\n\nThis is also why no scanner will find it. A static analyser can see that a query runs; it cannot know who should be allowed to run it.\n\n> Figure: What a scanner can reach. A two by two grid, rows Mechanical classes and Missing authorisation, columns Found by a tool? and Why: Mechanical classes and Found by a tool?: Yes; Mechanical classes and Why: A pattern exists to match; Missing authorisation and Found by a tool?: No; Missing authorisation and Why: The rule is in nobody's corpus. Authentication asks whether this is a logged-in user. Authorisation asks whether the invoice is theirs, and it is the most common serious finding in real applications.\n\n## The measurements, and what they are worth\n\nTwo studies are worth knowing.\n\nPearce and colleagues, *Asleep at the Keyboard* (2021), generated programs for scenarios drawn from the MITRE top-25 weakness list and found roughly 40 per cent of the resulting programs contained a relevant vulnerability.\n\nPerry and colleagues at Stanford (published 2023) ran a controlled study with participants writing security-sensitive functions with and without an assistant. Those with the assistant wrote less secure code, and, separately, were **more likely to believe their code was secure**. That second result is the one to carry. It is the competence illusion from module seven, measured on the outcome that hurts most.\n\nBoth studies used models that are now several generations old, in constrained settings, with small samples. The specific percentages should not be quoted as current. The mechanism (a distribution containing insecure patterns, plus a reader whose confidence tracks fluency) has not changed.\n\n## What to do\n\n**Make the authorisation question a required review step.** One sentence: *show me the line that checks this user is allowed to touch this object.* If the answer is a gesture at middleware, ask which middleware and what it checks. This single question finds more real vulnerabilities than any tool you can buy.\n\n**Ask for the secure variant explicitly, and still check.** Adding \"use parameterised queries and a constant-time comparison\" to a request measurably improves the output, and it does not make the output trustworthy. It shifts the distribution; it does not verify anything.\n\n**Put the mechanical classes in the pipeline.** Free tools catch concatenated SQL, weak hashes and disabled verification reliably: that is the next lesson. Automating the mechanical classes is how you buy human attention for the authorisation question, which is the one that needs a person.\n\n**Write the negative test.** For every endpoint that returns something owned by somebody: a test where user B requests user A's object and expects a 403 or 404. Generated suites almost never contain this test, because the happy path is what examples show."},"check":{"question":"Why can a static analyser reliably flag string-concatenated SQL but never flag a missing ownership check on an invoice endpoint?","options":[{"text":"Authorisation bugs occur at runtime, and static analysers only examine code that does not execute.","correct":false,"tempting":"SQL injection is also a runtime failure; static tools reason about code paths in both cases."},{"text":"Authorisation logic usually lives in middleware, which analysers cannot trace across framework boundaries.","correct":false,"tempting":"A real engineering difficulty, but the rule would still be unknowable even with perfect cross-framework tracing."},{"text":"Concatenation is a syntactic pattern; who may read an invoice is a rule absent from the code.","correct":true},{"text":"Analysers are trained on vulnerability corpora that under-represent authorisation flaws relative to injection flaws.","correct":false,"tempting":"Attributes it to training data, but rule-based analysers are not trained and the limitation is the same."}]},"prev":{"slug":"packages-that-do-not-exist","title":"The package that did not exist until an attacker made it","url":"https://addaly.com/learn/how-software-changed/packages-that-do-not-exist"},"next":{"slug":"what-leaves-the-building","title":"What leaves the building when you type","url":"https://addaly.com/learn/how-software-changed/what-leaves-the-building"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Insecure defaults, inherited', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/vulnerabilities-that-arrive-by-default (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Insecure defaults, inherited","courseTitle":"How Software Development Changed","module":{"index":8,"slug":"provenance-security-and-law","title":"Provenance, security and the unsettled law"},"answer":null,"keyIdea":"Generated code inherits the insecure defaults of old public code, and the one class that no scanner can catch is missing authorisation, because who may touch which object is a business rule that exists in nobody’s training corpus.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"cols":["Found by a tool?","Why"],"rows":["Mechanical classes","Missing authorisation"],"type":"matrix","cells":[["Yes","A pattern exists to match"],["No","The rule is in nobody’s corpus"]],"title":"What a scanner can reach","caption":"Authentication asks whether this is a logged-in user. Authorisation asks whether the invoice is theirs, and it is the most common serious finding in real applications.","cellLabels":[["concatenated SQL, MD5 for passwords, verification switched off, pickle on a request body","so put them in the pipeline and buy human attention with the time"],["who may read this invoice is a sentence somebody said in a meeting","so ask for the line number that checks it, on every change"]]},"alt":"Figure: What a scanner can reach. A two by two grid, rows Mechanical classes and Missing authorisation, columns Found by a tool? and Why: Mechanical classes and Found by a tool?: Yes; Mechanical classes and Why: A pattern exists to match; Missing authorisation and Found by a tool?: No; Missing authorisation and Why: The rule is in nobody’s corpus. Authentication asks whether this is a logged-in user. Authorisation asks whether the invoice is theirs, and it is the most common serious finding in real applications."}},"blocks":[{"t":"h2","id":"where-the-insecurity-comes-from","runs":[{"text":"Where the insecurity comes from"}]},{"t":"p","runs":[{"text":"A generator produces what is typical in its training data. Its training data is public code. Public code is, on average, old, written under deadline, copied from an answer site, and never security-reviewed. The output inherits that distribution, and the inheritance is mechanical rather than malicious."}]},{"t":"p","runs":[{"text":"So the defects are not exotic. They are the ordinary ones, arriving at the rate at which you generate."}]},{"t":"h2","id":"what-actually-turns-up","runs":[{"text":"What actually turns up"}]},{"t":"p","runs":[{"text":"String-built SQL.","b":true},{"text":" Parameterised queries are common in good code and concatenation is common in tutorial code. Both are in the corpus."}]},{"t":"p","runs":[{"text":"Old cryptography.","b":true},{"text":" MD5 or SHA-1 for password storage, because a decade of examples did that. ECB mode. A hard-coded initialisation vector. "},{"text":"Math.random()","code":true},{"text":" or an unseeded PRNG for a session token or a password reset link, which is a full account takeover rather than a subtle weakness."}]},{"t":"p","runs":[{"text":"Verification switched off.","b":true},{"text":" "},{"text":"verify=False","code":true},{"text":" on a Python request, "},{"text":"rejectUnauthorized: false","code":true},{"text":" in Node, "},{"text":"InsecureSkipVerify: true","code":true},{"text":" in Go. These appear constantly in public code because they are how people get past a certificate error while debugging, and the debugging line ships."}]},{"t":"p","runs":[{"text":"Deserialisation of untrusted input.","b":true},{"text":" "},{"text":"pickle.loads","code":true},{"text":" on a request body is remote code execution, stated plainly."}]},{"t":"p","runs":[{"text":"Permissive by default.","b":true},{"text":" CORS set to "},{"text":"*","code":true},{"text":", an S3 bucket policy wider than needed, a database user with more rights than the application uses, an XML parser with external entities enabled."}]},{"t":"h2","id":"the-class-no-tool-finds","runs":[{"text":"The class no tool finds"}]},{"t":"p","runs":[{"text":"One category is different in kind, and it is the most common serious finding in real applications: "},{"text":"missing authorisation","b":true},{"text":"."}]},{"t":"p","runs":[{"text":"Ask for an endpoint that returns an invoice and you will very likely get authentication (is this a logged-in user) and very likely not get authorisation: is this "},{"text":"their","i":true},{"text":" invoice."}]},{"t":"code","lang":"python","label":"Python","text":"@app.get(\"/invoices/{invoice_id}\")\ndef get_invoice(invoice_id: int, user = Depends(current_user)):\n    return db.query(Invoice).get(invoice_id)   # whose invoice?"},{"t":"p","runs":[{"text":"That code is correct-looking, passes its tests, and lets any logged-in customer read every invoice in the system by changing a number in the URL. It is the top entry in the OWASP application security risks list for a reason."}]},{"t":"p","runs":[{"text":"The mechanism is worth being precise about. The model cannot know this is wrong, because the rule it violates is not in the code. It lives in a sentence somebody said in a meeting: "},{"text":"a customer can see their own invoices and an administrator can see all of them, except finance staff, who can see any invoice but cannot see the customer’s address.","i":true},{"text":" Nothing in any corpus contains that. It is not a knowledge gap; it is an information that does not exist outside your organisation."}]},{"t":"p","runs":[{"text":"This is also why no scanner will find it. A static analyser can see that a query runs; it cannot know who should be allowed to run it."}]},{"t":"h2","id":"the-measurements-and-what-they-are-worth","runs":[{"text":"The measurements, and what they are worth"}]},{"t":"p","runs":[{"text":"Two studies are worth knowing."}]},{"t":"p","runs":[{"text":"Pearce and colleagues, "},{"text":"Asleep at the Keyboard","i":true},{"text":" (2021), generated programs for scenarios drawn from the MITRE top-25 weakness list and found roughly 40 per cent of the resulting programs contained a relevant vulnerability."}]},{"t":"p","runs":[{"text":"Perry and colleagues at Stanford (published 2023) ran a controlled study with participants writing security-sensitive functions with and without an assistant. Those with the assistant wrote less secure code, and, separately, were "},{"text":"more likely to believe their code was secure","b":true},{"text":". That second result is the one to carry. It is the competence illusion from module seven, measured on the outcome that hurts most."}]},{"t":"p","runs":[{"text":"Both studies used models that are now several generations old, in constrained settings, with small samples. The specific percentages should not be quoted as current. The mechanism (a distribution containing insecure patterns, plus a reader whose confidence tracks fluency) has not changed."}]},{"t":"h2","id":"what-to-do","runs":[{"text":"What to do"}]},{"t":"p","runs":[{"text":"Make the authorisation question a required review step.","b":true},{"text":" One sentence: "},{"text":"show me the line that checks this user is allowed to touch this object.","i":true},{"text":" If the answer is a gesture at middleware, ask which middleware and what it checks. This single question finds more real vulnerabilities than any tool you can buy."}]},{"t":"p","runs":[{"text":"Ask for the secure variant explicitly, and still check.","b":true},{"text":" Adding “use parameterised queries and a constant-time comparison” to a request measurably improves the output, and it does not make the output trustworthy. It shifts the distribution; it does not verify anything."}]},{"t":"p","runs":[{"text":"Put the mechanical classes in the pipeline.","b":true},{"text":" Free tools catch concatenated SQL, weak hashes and disabled verification reliably: that is the next lesson. Automating the mechanical classes is how you buy human attention for the authorisation question, which is the one that needs a person."}]},{"t":"p","runs":[{"text":"Write the negative test.","b":true},{"text":" For every endpoint that returns something owned by somebody: a test where user B requests user A’s object and expects a 403 or 404. Generated suites almost never contain this test, because the happy path is what examples show."}]}],"check":{"question":[{"text":"Why can a static analyser reliably flag string-concatenated SQL but never flag a missing ownership check on an invoice endpoint?"}],"options":[{"text":[{"text":"Authorisation bugs occur at runtime, and static analysers only examine code that does not execute."}],"correct":false,"tempting":[{"text":"SQL injection is also a runtime failure; static tools reason about code paths in both cases."}]},{"text":[{"text":"Authorisation logic usually lives in middleware, which analysers cannot trace across framework boundaries."}],"correct":false,"tempting":[{"text":"A real engineering difficulty, but the rule would still be unknowable even with perfect cross-framework tracing."}]},{"text":[{"text":"Concatenation is a syntactic pattern; who may read an invoice is a rule absent from the code."}],"correct":true,"tempting":null},{"text":[{"text":"Analysers are trained on vulnerability corpora that under-represent authorisation flaws relative to injection flaws."}],"correct":false,"tempting":[{"text":"Attributes it to training data, but rule-based analysers are not trained and the limitation is the same."}]}],"order":[3,2,0,1],"why":null},"prev":{"slug":"packages-that-do-not-exist","title":"The package that did not exist until an attacker made it","minutes":9},"next":{"slug":"what-leaves-the-building","title":"What leaves the building when you type","minutes":9}}}