sloptic 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sloptic-1.1.0/.gitignore +36 -0
- sloptic-1.1.0/CONTRIBUTING.md +162 -0
- sloptic-1.1.0/CORPUS_REPORT.md +220 -0
- sloptic-1.1.0/LICENSE +201 -0
- sloptic-1.1.0/PKG-INFO +177 -0
- sloptic-1.1.0/README.md +156 -0
- sloptic-1.1.0/RELEASE_NOTES.md +135 -0
- sloptic-1.1.0/catalog/performance/perf-cache-001.yaml +15 -0
- sloptic-1.1.0/catalog/performance/perf-compress-001.yaml +13 -0
- sloptic-1.1.0/catalog/performance/perf-cwv-001.yaml +14 -0
- sloptic-1.1.0/catalog/performance/perf-cwv-002.yaml +17 -0
- sloptic-1.1.0/catalog/performance/perf-load-001.yaml +12 -0
- sloptic-1.1.0/catalog/performance/perf-loadtime-001.yaml +12 -0
- sloptic-1.1.0/catalog/performance/perf-requests-001.yaml +12 -0
- sloptic-1.1.0/catalog/performance/perf-ttfb-001.yaml +13 -0
- sloptic-1.1.0/catalog/performance/perf-ttfb-002.yaml +14 -0
- sloptic-1.1.0/catalog/performance/perf-ttfb-003.yaml +14 -0
- sloptic-1.1.0/catalog/performance/perf-weight-001.yaml +14 -0
- sloptic-1.1.0/catalog/performance/perf-weight-002.yaml +14 -0
- sloptic-1.1.0/catalog/qa/qa-a11y-001.yaml +23 -0
- sloptic-1.1.0/catalog/qa/qa-a11y-002.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-backnav-001.yaml +15 -0
- sloptic-1.1.0/catalog/qa/qa-chunk-001.yaml +14 -0
- sloptic-1.1.0/catalog/qa/qa-console-001.yaml +12 -0
- sloptic-1.1.0/catalog/qa/qa-crash-010.yaml +17 -0
- sloptic-1.1.0/catalog/qa/qa-ctype-001.yaml +12 -0
- sloptic-1.1.0/catalog/qa/qa-deadctrl-001.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-deeplink-001.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-devbuild-001.yaml +47 -0
- sloptic-1.1.0/catalog/qa/qa-errhyg-001.yaml +12 -0
- sloptic-1.1.0/catalog/qa/qa-http-001.yaml +13 -0
- sloptic-1.1.0/catalog/qa/qa-http-002.yaml +13 -0
- sloptic-1.1.0/catalog/qa/qa-input-001.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-integrity-001.yaml +12 -0
- sloptic-1.1.0/catalog/qa/qa-integrity-002.yaml +14 -0
- sloptic-1.1.0/catalog/qa/qa-links-001.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-noerror-001.yaml +17 -0
- sloptic-1.1.0/catalog/qa/qa-race-001.yaml +12 -0
- sloptic-1.1.0/catalog/qa/qa-race-002.yaml +16 -0
- sloptic-1.1.0/catalog/qa/qa-seo-001.yaml +14 -0
- sloptic-1.1.0/catalog/qa/qa-staleui-001.yaml +16 -0
- sloptic-1.1.0/catalog/security/sec-authbypass-001.yaml +14 -0
- sloptic-1.1.0/catalog/security/sec-backend-001.yaml +14 -0
- sloptic-1.1.0/catalog/security/sec-backend-002.yaml +20 -0
- sloptic-1.1.0/catalog/security/sec-backend-003.yaml +24 -0
- sloptic-1.1.0/catalog/security/sec-cmdi-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-cors-001.yaml +14 -0
- sloptic-1.1.0/catalog/security/sec-csp-001.yaml +15 -0
- sloptic-1.1.0/catalog/security/sec-csrf-001.yaml +10 -0
- sloptic-1.1.0/catalog/security/sec-debug-001.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-deps-001.yaml +15 -0
- sloptic-1.1.0/catalog/security/sec-domxss-001.yaml +10 -0
- sloptic-1.1.0/catalog/security/sec-dos-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-exposure-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-exposure-002.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-exposure-003.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-exposure-004.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-exposure-005.yaml +14 -0
- sloptic-1.1.0/catalog/security/sec-exposure-006.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-exposure-007.yaml +18 -0
- sloptic-1.1.0/catalog/security/sec-exposure-008.yaml +25 -0
- sloptic-1.1.0/catalog/security/sec-filterinj-001.yaml +28 -0
- sloptic-1.1.0/catalog/security/sec-headers-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-headers-002.yaml +19 -0
- sloptic-1.1.0/catalog/security/sec-headers-003.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-headers-004.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-headers-005.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-headers-006.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-hosthdr-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-idor-001.yaml +10 -0
- sloptic-1.1.0/catalog/security/sec-idor-002.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-idor-003.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-idor-004.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-idor-005.yaml +19 -0
- sloptic-1.1.0/catalog/security/sec-lfi-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-mixed-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-ratelimit-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-redirect-001.yaml +10 -0
- sloptic-1.1.0/catalog/security/sec-secrets-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-secrets-002.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-session-001.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-session-002.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-session-003.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-session-004.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-session-005.yaml +11 -0
- sloptic-1.1.0/catalog/security/sec-split-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-sqli-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-sqli-002.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-sqli-003.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-sqli-004.yaml +17 -0
- sloptic-1.1.0/catalog/security/sec-sqli-005.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-ssrf-001.yaml +10 -0
- sloptic-1.1.0/catalog/security/sec-ssti-001.yaml +13 -0
- sloptic-1.1.0/catalog/security/sec-upload-001.yaml +12 -0
- sloptic-1.1.0/catalog/security/sec-upload-002.yaml +15 -0
- sloptic-1.1.0/catalog/security/sec-xss-001.yaml +19 -0
- sloptic-1.1.0/catalog/security/sec-xss-002.yaml +17 -0
- sloptic-1.1.0/catalog/security/sec-xxe-001.yaml +10 -0
- sloptic-1.1.0/claude.md +448 -0
- sloptic-1.1.0/pyproject.toml +59 -0
- sloptic-1.1.0/references/hardened/Dockerfile +8 -0
- sloptic-1.1.0/references/hardened/app.py +242 -0
- sloptic-1.1.0/references/jsonapi/app.py +183 -0
- sloptic-1.1.0/references/minimal/Dockerfile +8 -0
- sloptic-1.1.0/references/minimal/app.py +41 -0
- sloptic-1.1.0/references/qa-janky/app.py +79 -0
- sloptic-1.1.0/references/spa/app.py +73 -0
- sloptic-1.1.0/references/vulnerable/Dockerfile +8 -0
- sloptic-1.1.0/references/vulnerable/app.py +248 -0
- sloptic-1.1.0/scripts/benchmark.py +458 -0
- sloptic-1.1.0/scripts/charts.py +267 -0
- sloptic-1.1.0/scripts/deploy_and_grade.py +1489 -0
- sloptic-1.1.0/scripts/devpost_repos.py +287 -0
- sloptic-1.1.0/scripts/gapbench_run.py +418 -0
- sloptic-1.1.0/scripts/gapbench_score.py +300 -0
- sloptic-1.1.0/scripts/list_probes.py +103 -0
- sloptic-1.1.0/scripts/parity.py +262 -0
- sloptic-1.1.0/scripts/precision.py +408 -0
- sloptic-1.1.0/scripts/report_card.py +107 -0
- sloptic-1.1.0/scripts/run_batch.py +600 -0
- sloptic-1.1.0/scripts/stats.py +603 -0
- sloptic-1.1.0/sloptic/__init__.py +7 -0
- sloptic-1.1.0/sloptic/aggregate.py +120 -0
- sloptic-1.1.0/sloptic/auth.py +796 -0
- sloptic-1.1.0/sloptic/baas.py +157 -0
- sloptic-1.1.0/sloptic/browser.py +1377 -0
- sloptic-1.1.0/sloptic/catalog.py +69 -0
- sloptic-1.1.0/sloptic/cli.py +362 -0
- sloptic-1.1.0/sloptic/deploy.py +266 -0
- sloptic-1.1.0/sloptic/depscan.py +66 -0
- sloptic-1.1.0/sloptic/discovery.py +1355 -0
- sloptic-1.1.0/sloptic/ingest.py +84 -0
- sloptic-1.1.0/sloptic/jsmine.py +59 -0
- sloptic-1.1.0/sloptic/jsonl.py +22 -0
- sloptic-1.1.0/sloptic/net.py +106 -0
- sloptic-1.1.0/sloptic/oob.py +68 -0
- sloptic-1.1.0/sloptic/openapi.py +164 -0
- sloptic-1.1.0/sloptic/perf.py +71 -0
- sloptic-1.1.0/sloptic/pipeline.py +300 -0
- sloptic-1.1.0/sloptic/platform_id.py +138 -0
- sloptic-1.1.0/sloptic/probes.py +5483 -0
- sloptic-1.1.0/sloptic/provenance.py +118 -0
- sloptic-1.1.0/sloptic/reportcard.py +424 -0
- sloptic-1.1.0/sloptic/safety.py +64 -0
- sloptic-1.1.0/sloptic/schema.py +152 -0
- sloptic-1.1.0/sloptic/scope.py +45 -0
- sloptic-1.1.0/sloptic/secretscan.py +218 -0
- sloptic-1.1.0/sloptic/vendor/axe.min.js +12 -0
- sloptic-1.1.0/tests/test_a11y.py +83 -0
- sloptic-1.1.0/tests/test_aggregate.py +89 -0
- sloptic-1.1.0/tests/test_anon_bulk_and_schema.py +270 -0
- sloptic-1.1.0/tests/test_api_injection.py +330 -0
- sloptic-1.1.0/tests/test_audit_timeout.py +99 -0
- sloptic-1.1.0/tests/test_auth.py +197 -0
- sloptic-1.1.0/tests/test_auth_browser.py +93 -0
- sloptic-1.1.0/tests/test_auth_header.py +84 -0
- sloptic-1.1.0/tests/test_auth_modern_stack.py +187 -0
- sloptic-1.1.0/tests/test_auth_spa.py +63 -0
- sloptic-1.1.0/tests/test_authbypass.py +87 -0
- sloptic-1.1.0/tests/test_baas_module.py +185 -0
- sloptic-1.1.0/tests/test_baas_service_keys.py +109 -0
- sloptic-1.1.0/tests/test_backend_exposure.py +345 -0
- sloptic-1.1.0/tests/test_backend_observed_tables.py +57 -0
- sloptic-1.1.0/tests/test_backnav.py +71 -0
- sloptic-1.1.0/tests/test_batch_throttle.py +65 -0
- sloptic-1.1.0/tests/test_batch_tldr.py +58 -0
- sloptic-1.1.0/tests/test_batch_wedge.py +155 -0
- sloptic-1.1.0/tests/test_benchmark.py +167 -0
- sloptic-1.1.0/tests/test_bola_collection.py +136 -0
- sloptic-1.1.0/tests/test_bola_managed_backend.py +171 -0
- sloptic-1.1.0/tests/test_browser.py +635 -0
- sloptic-1.1.0/tests/test_browser_preflight.py +51 -0
- sloptic-1.1.0/tests/test_bundle_api_mining.py +151 -0
- sloptic-1.1.0/tests/test_caching.py +84 -0
- sloptic-1.1.0/tests/test_catalog_integrity.py +76 -0
- sloptic-1.1.0/tests/test_chunk.py +71 -0
- sloptic-1.1.0/tests/test_cli.py +132 -0
- sloptic-1.1.0/tests/test_cmdi.py +108 -0
- sloptic-1.1.0/tests/test_contrast_tiering.py +111 -0
- sloptic-1.1.0/tests/test_conventional_api_probe.py +133 -0
- sloptic-1.1.0/tests/test_conventional_pairs.py +140 -0
- sloptic-1.1.0/tests/test_coverage_gate.py +187 -0
- sloptic-1.1.0/tests/test_crash.py +80 -0
- sloptic-1.1.0/tests/test_create_read_pair.py +193 -0
- sloptic-1.1.0/tests/test_debug_mode.py +99 -0
- sloptic-1.1.0/tests/test_decompression_bomb.py +92 -0
- sloptic-1.1.0/tests/test_deeplink.py +83 -0
- sloptic-1.1.0/tests/test_deploy_meta.py +346 -0
- sloptic-1.1.0/tests/test_devbuild.py +158 -0
- sloptic-1.1.0/tests/test_devpost.py +148 -0
- sloptic-1.1.0/tests/test_discover_auth_crawl.py +74 -0
- sloptic-1.1.0/tests/test_discovery.py +722 -0
- sloptic-1.1.0/tests/test_docker_deploy.py +45 -0
- sloptic-1.1.0/tests/test_docker_hardened.py +113 -0
- sloptic-1.1.0/tests/test_exposed_files.py +150 -0
- sloptic-1.1.0/tests/test_filter_injection.py +157 -0
- sloptic-1.1.0/tests/test_gapbench_run.py +252 -0
- sloptic-1.1.0/tests/test_gapbench_score.py +208 -0
- sloptic-1.1.0/tests/test_grade_timeout.py +50 -0
- sloptic-1.1.0/tests/test_host_header.py +63 -0
- sloptic-1.1.0/tests/test_http_conformance.py +58 -0
- sloptic-1.1.0/tests/test_https_pipeline.py +73 -0
- sloptic-1.1.0/tests/test_idor_user_record.py +108 -0
- sloptic-1.1.0/tests/test_ingest.py +75 -0
- sloptic-1.1.0/tests/test_innocence_check.py +245 -0
- sloptic-1.1.0/tests/test_jsmine.py +35 -0
- sloptic-1.1.0/tests/test_json_injection.py +113 -0
- sloptic-1.1.0/tests/test_jsonl.py +23 -0
- sloptic-1.1.0/tests/test_lfi.py +104 -0
- sloptic-1.1.0/tests/test_links.py +87 -0
- sloptic-1.1.0/tests/test_liveness_vs_ttfb.py +88 -0
- sloptic-1.1.0/tests/test_login_creds.py +84 -0
- sloptic-1.1.0/tests/test_mixed_content.py +93 -0
- sloptic-1.1.0/tests/test_noerror.py +82 -0
- sloptic-1.1.0/tests/test_openapi.py +100 -0
- sloptic-1.1.0/tests/test_parity.py +74 -0
- sloptic-1.1.0/tests/test_perf.py +154 -0
- sloptic-1.1.0/tests/test_pipeline.py +288 -0
- sloptic-1.1.0/tests/test_platform.py +74 -0
- sloptic-1.1.0/tests/test_precision.py +129 -0
- sloptic-1.1.0/tests/test_probe_selection.py +87 -0
- sloptic-1.1.0/tests/test_probes.py +280 -0
- sloptic-1.1.0/tests/test_qa_data_spa.py +121 -0
- sloptic-1.1.0/tests/test_qa_janky.py +28 -0
- sloptic-1.1.0/tests/test_rate_limited_fetch.py +109 -0
- sloptic-1.1.0/tests/test_record_contract.py +52 -0
- sloptic-1.1.0/tests/test_redirect_oauth.py +128 -0
- sloptic-1.1.0/tests/test_remote.py +49 -0
- sloptic-1.1.0/tests/test_reportcard.py +78 -0
- sloptic-1.1.0/tests/test_repro.py +67 -0
- sloptic-1.1.0/tests/test_response_splitting.py +62 -0
- sloptic-1.1.0/tests/test_safety.py +49 -0
- sloptic-1.1.0/tests/test_scenario_selection.py +70 -0
- sloptic-1.1.0/tests/test_schema_discovery.py +166 -0
- sloptic-1.1.0/tests/test_scope.py +27 -0
- sloptic-1.1.0/tests/test_search_endpoint_discovery.py +220 -0
- sloptic-1.1.0/tests/test_secretscan.py +78 -0
- sloptic-1.1.0/tests/test_secretscan_config_formats.py +106 -0
- sloptic-1.1.0/tests/test_seo.py +76 -0
- sloptic-1.1.0/tests/test_session_cookie_pick.py +276 -0
- sloptic-1.1.0/tests/test_session_storage.py +111 -0
- sloptic-1.1.0/tests/test_soft404.py +102 -0
- sloptic-1.1.0/tests/test_spa_secrets.py +82 -0
- sloptic-1.1.0/tests/test_sqli_error_breadth.py +130 -0
- sloptic-1.1.0/tests/test_sqli_prefix_catchall.py +65 -0
- sloptic-1.1.0/tests/test_ssrf.py +61 -0
- sloptic-1.1.0/tests/test_ssrf_inband.py +96 -0
- sloptic-1.1.0/tests/test_ssti.py +86 -0
- sloptic-1.1.0/tests/test_staleui.py +133 -0
- sloptic-1.1.0/tests/test_stored_xss_api.py +92 -0
- sloptic-1.1.0/tests/test_subpath_targets.py +72 -0
- sloptic-1.1.0/tests/test_supabase_selfhosted_rls.py +188 -0
- sloptic-1.1.0/tests/test_trace.py +117 -0
- sloptic-1.1.0/tests/test_upload.py +89 -0
- sloptic-1.1.0/tests/test_upload_xss.py +137 -0
- sloptic-1.1.0/tests/test_weak_session.py +64 -0
- sloptic-1.1.0/tests/test_xss.py +94 -0
- sloptic-1.1.0/tests/test_xxe.py +72 -0
- sloptic-1.1.0/uv.lock +474 -0
- sloptic-1.1.0/validation/benchmark-curve.json +9291 -0
- sloptic-1.1.0/validation/vuln-corpus/README.md +101 -0
- sloptic-1.1.0/validation/vuln-corpus/anchors.txt +32 -0
- sloptic-1.1.0/validation/vuln-corpus/docker-compose.yml +40 -0
- sloptic-1.1.0/validation/vuln-corpus/gapbench-manifest.json +1 -0
- sloptic-1.1.0/validation/vuln-corpus/gapbench.txt +112 -0
- sloptic-1.1.0/validation/vuln-corpus/serve-anchors.sh +80 -0
- sloptic-1.1.0/validation/vuln-corpus/setup.sh +29 -0
sloptic-1.1.0/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
|
|
6
|
+
# Hidden-pool probes must NEVER be committed to this PUBLIC repo. The real boundary is a
|
|
7
|
+
# separate PRIVATE hidden-probe catalog repo; this is only a backstop.
|
|
8
|
+
**/hidden/
|
|
9
|
+
catalog-hidden/
|
|
10
|
+
|
|
11
|
+
# GRADING OUTPUT MUST NOT BE PUBLISHED. A results file names real third-party apps and carries, per finding, a
|
|
12
|
+
# paste-to-reproduce request against a live deployment — the v9/v10 corpus runs alone held 1,709 named Devpost
|
|
13
|
+
# projects and 8,825 repro blocks. Black-box scanning of a public URL is one thing; publishing per-app
|
|
14
|
+
# reproduction steps against someone else's submission is a different act, and it contradicts the product's own
|
|
15
|
+
# guardrail (hand the builder back a private report, upside-only).
|
|
16
|
+
#
|
|
17
|
+
# THIS FILE IS THE SOURCE OF TRUTH FOR BOTH REPOS, and that is the whole point. The league -> mirror sync is
|
|
18
|
+
# `rsync --delete` followed by `git add -A`, so rsync OVERWRITES the mirror's .gitignore with this one on every
|
|
19
|
+
# sync — a fix applied only on the mirror side survives until the next sync and then silently un-ignores
|
|
20
|
+
# everything (observed, not hypothesised). The sync command also carries matching --exclude flags; this is the
|
|
21
|
+
# backstop for when one is forgotten.
|
|
22
|
+
#
|
|
23
|
+
# *.jsonl and NOT *.json: validation/benchmark-curve.json is the frozen scoring curve and must stay tracked.
|
|
24
|
+
*.jsonl
|
|
25
|
+
*.log
|
|
26
|
+
*.tsv
|
|
27
|
+
v9-baseline-stats.json
|
|
28
|
+
nextjs-trace-sample.txt
|
|
29
|
+
sprint-sample.txt
|
|
30
|
+
terminal output of test.md
|
|
31
|
+
|
|
32
|
+
# Diagnostic URL worklists carry hundreds of named third-party apps (the v11 session-gap list is 311). Same
|
|
33
|
+
# reasoning as the grading output above: black-box scanning a public URL is one thing, publishing the list of
|
|
34
|
+
# who we scanned is another. Keep the *pattern* here so a regenerated list is covered without anyone rememb-
|
|
35
|
+
# ering to add it.
|
|
36
|
+
*.urls.txt
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Authoring probes
|
|
2
|
+
|
|
3
|
+
How to add, change, and remove probes. The engine is **fixed**; **probes are data**
|
|
4
|
+
(`catalog/**/*.yaml`, loaded by `load_catalog()` at run time). Reusing existing detection needs no
|
|
5
|
+
code change, only a *new* detection primitive touches Python. This is the practical recipe.
|
|
6
|
+
|
|
7
|
+
The live lists of detection primitives are **`MATCHERS` and `PREDICATES` in `sloptic/probes.py`**,
|
|
8
|
+
the source of truth (this doc deliberately does not enumerate them, so it cannot drift). Skim those
|
|
9
|
+
dicts to see everything that exists today.
|
|
10
|
+
|
|
11
|
+
## The probe schema
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
id: sec-sqli-001 # unique: the LOADER KEYS ON THIS, NOT THE FILENAME (a dup id collides even
|
|
15
|
+
# across files with different names)
|
|
16
|
+
bundle: security # security | qa | performance
|
|
17
|
+
category: sql-injection # diminishing-returns damper applies WITHIN a category (sorted descending decay)
|
|
18
|
+
variant_group_id: <id> # optional; probes sharing one fire ONCE at the max penalty (same logical flaw)
|
|
19
|
+
pool: public # public | hidden
|
|
20
|
+
evidence_model: provable # provable (slop visible in a response) | oracle (differential / self-as-oracle)
|
|
21
|
+
penalty: 40 # slop added when it fires (deduction-only; always positive)
|
|
22
|
+
applicability:
|
|
23
|
+
requires: [any_endpoint_accepts_text_input] # capabilities from discovery; empty = always applicable
|
|
24
|
+
probe:
|
|
25
|
+
# EITHER declarative (fetch a target, apply matchers):
|
|
26
|
+
method: POST
|
|
27
|
+
target: /search # a literal path, or the selector "routes"/"forms" to fan across the surface
|
|
28
|
+
query: { q: "<payload>" } # optional query params
|
|
29
|
+
data: { age: abc } # optional form body
|
|
30
|
+
# OR a predicate (multi-step oracle):
|
|
31
|
+
predicate: sqli_auth_bypass
|
|
32
|
+
payload: "' OR '1'='1' -- " # arbitrary keys under probe: are read by the predicate via probe.probe
|
|
33
|
+
slop_if: # declarative ONLY; ALL must match -> slop. Omit for predicate probes.
|
|
34
|
+
- response_contains: "<payload>"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## The two detection primitives (contracts)
|
|
38
|
+
|
|
39
|
+
**Matcher**: `MATCHERS[name](resp, arg=None) -> bool`. A pure function over ONE response, `True` when
|
|
40
|
+
slop is present. For declarative probes.
|
|
41
|
+
|
|
42
|
+
**Predicate**: `PREDICATES[name](ctx, probe) -> bool | None`. A multi-step oracle. **Three-state, and
|
|
43
|
+
the third state is load-bearing:**
|
|
44
|
+
|
|
45
|
+
| return | meaning |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `True` | slop detected → the probe's `penalty` applies |
|
|
48
|
+
| `False` | clean → tested and the flaw is absent |
|
|
49
|
+
| `None` | **not applicable**: could not establish the conditions to test (no such surface, self-registration failed, and so on). This is NOT a clean pass. A `False` when you could not actually test is a **false clean, a missed finding.** When in doubt between `False` and `None`, return `None`. |
|
|
50
|
+
|
|
51
|
+
The `ctx` object gives a predicate: `ctx.base_url`, `ctx.headers` (auth), `ctx.profile` (the discovered
|
|
52
|
+
surface: `.routes`, `.forms`, `.endpoints`, `.capabilities`), `ctx.client` (a shared httpx client), and
|
|
53
|
+
`ctx.evidence` (see below). For a fresh, correctly authenticated client use
|
|
54
|
+
`make_client(ctx.base_url, ctx.headers)` (seeds the cookie into the jar so a rotating session is
|
|
55
|
+
followed; defaults `verify=False` so an https target with a self-signed cert is reachable).
|
|
56
|
+
|
|
57
|
+
### Evidence (required for a new predicate)
|
|
58
|
+
|
|
59
|
+
Every predicate should record what it measured or attempted via `ctx.evidence.update(key=value, ...)`,
|
|
60
|
+
for **all** outcomes, clean and n/a too, not just slop. It rides in `--json` and the `-v` view, and is
|
|
61
|
+
the product's transparency ("load_time_s=0.4 ✓", "tried error+boolean+union+time, none hit"). Keep it
|
|
62
|
+
small and typed (numbers, short strings, small lists). Example:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
def perf_ttfb(ctx, probe) -> bool:
|
|
66
|
+
thresh = perf.TTFB_PROFILE
|
|
67
|
+
with make_client(ctx.base_url, ctx.headers) as c:
|
|
68
|
+
sample = perf.sample_ttfb(c, "/")
|
|
69
|
+
ctx.evidence.update(ttfb_s=round(sample, 3), threshold_s=thresh) # <- what it measured
|
|
70
|
+
return sample >= thresh
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Safety (a probe fetches an UNTRUSTED, possibly authenticated target)
|
|
74
|
+
|
|
75
|
+
- **Never GET a discovered `<a href>` navigation or a state-changing endpoint with the auth cookie.**
|
|
76
|
+
A blind fetch of `<a href="logout.php">` logs the grader's own session out mid-run and blinds every
|
|
77
|
+
later probe (this actually happened, `fix 8071b67`). Fetch only true subresources
|
|
78
|
+
(`<img/script/media src>`, `<link href>`), and skip logout/delete-looking links (see `broken_links`).
|
|
79
|
+
- Never send `PUT`/`PATCH`/`DELETE` payloads that mutate the target's state; grading must be safe to
|
|
80
|
+
re-run. Read-only injection (`GET`/benign `POST`) only.
|
|
81
|
+
- A predicate that raises is caught and degraded to N/A for that one probe (the run never DNFs), but
|
|
82
|
+
do not rely on it; handle `httpx.HTTPError`/`InvalidURL` yourself.
|
|
83
|
+
|
|
84
|
+
## Add a probe
|
|
85
|
+
|
|
86
|
+
### 1. Declarative, reusing a matcher: one file, no code
|
|
87
|
+
Drop a YAML in `catalog/<bundle>/` with a `target` + `slop_if` using an existing matcher.
|
|
88
|
+
|
|
89
|
+
### 2. A variant of an existing class: one file, same `variant_group_id`
|
|
90
|
+
A new SQLi syntax or XSS payload: copy a sibling YAML, change the `payload`, keep the
|
|
91
|
+
`variant_group_id`. It reuses the oracle and folds into the fire-once group (the group counts one
|
|
92
|
+
penalty no matter how many syntaxes fire). **Give it a unique `id`.**
|
|
93
|
+
|
|
94
|
+
### 3. A new detection primitive: +1 function, then the YAML
|
|
95
|
+
Add a `MATCHERS` or `PREDICATES` entry (and a matching `_MATCHER_REASONS`/`_PREDICATE_REASONS` line for
|
|
96
|
+
the human "why it fired"). Predicates emit `ctx.evidence`. **One finding per class, all its techniques:**
|
|
97
|
+
cover the class's techniques (SQLi = error/boolean/UNION/time; XSS = script/img/svg/attr/…) but collapse
|
|
98
|
+
them to ONE finding, a single predicate that returns once, or siblings sharing a `variant_group_id`.
|
|
99
|
+
Breadth is recall, not score inflation; keep each technique precise (marker, differential, confirmation
|
|
100
|
+
guards) so breadth does not cost false positives.
|
|
101
|
+
|
|
102
|
+
### CI-lock it (per-technique reference servers)
|
|
103
|
+
Add `tests/test_<name>.py` that stands up a throwaway `http.server` exhibiting exactly the flaw (one
|
|
104
|
+
per technique) and asserts the predicate fires, plus a clean server it must not fire on, plus the N/A
|
|
105
|
+
case. A fake `ctx` is `type("C", (), {"base_url": url, "headers": None, "client": None, "evidence": {}})()`.
|
|
106
|
+
Note the **`evidence: {}`** (a predicate writes to it; the full suite catches a missing one via
|
|
107
|
+
`AttributeError`).
|
|
108
|
+
|
|
109
|
+
## Change a probe
|
|
110
|
+
- **Tuning** (penalty, payload, threshold, applicability, pool) → edit the YAML field, re-run the suite.
|
|
111
|
+
- **Detection logic** → edit the matcher/predicate (affects every probe that uses it, so review
|
|
112
|
+
accordingly).
|
|
113
|
+
- **Pool flip** (public ↔ hidden) → change `pool:` and move the file between this public catalog and
|
|
114
|
+
the private hidden-probe catalog repo.
|
|
115
|
+
|
|
116
|
+
## Remove a probe
|
|
117
|
+
Delete the YAML and the runner stops loading it. Remove its assertion in `tests/`. For an event-grade
|
|
118
|
+
catalog, *deprecate in the changelog* rather than silently delete, so past results stay interpretable.
|
|
119
|
+
|
|
120
|
+
## Pricing a penalty (risk = frequency × severity)
|
|
121
|
+
A penalty is **expected harm**, not raw severity. Price it by how often a real user is hurt × how badly:
|
|
122
|
+
|
|
123
|
+
- **Security = low-frequency × terminal-severity.** A DB-dumping SQLi, auth bypass, or RCE is rare per
|
|
124
|
+
app (injection incidence ~3% of endpoints, Verizon DBIR / OWASP) but a single one is company-ending
|
|
125
|
+
(avg breach $4.4M, IBM 2025). These sit at the **per-instance ceiling (≈40)** and no other class
|
|
126
|
+
outranks a single one. Defense in depth (missing headers) is low × low, so it stays small.
|
|
127
|
+
- **QA / performance = high-frequency × moderate-severity.** Every visitor on slow 4G hits the slow
|
|
128
|
+
page (~53% bounce past 3s, Google 2016; ~79% never return); every wrong-order user hits the crash
|
|
129
|
+
(~32% churn after one bad experience, PwC 2018); ~16% of people are barred by a11y failures (WHO
|
|
130
|
+
2023). Priced **up toward the deadly range but strictly below the catastrophic-security ceiling**, so
|
|
131
|
+
no single qa/perf penalty is ≥ the worst single security penalty.
|
|
132
|
+
- **Net effect (deliberate):** the aggregate leans qa/perf (high-frequency harm stacks across many
|
|
133
|
+
probes and instances), while the per-incident ceiling stays with catastrophic security.
|
|
134
|
+
|
|
135
|
+
Do not multiply ordinal severities (Cox 2008, *What's Wrong with Risk Matrices?*, ordinal labels are
|
|
136
|
+
not cardinal). These magnitudes are a **designed table** with a consequence-triggered override band
|
|
137
|
+
(terminal severity keeps the ceiling regardless of low frequency), the practice NIST 800-30 /
|
|
138
|
+
MIL-STD-882E use.
|
|
139
|
+
|
|
140
|
+
## The calibration gate (non-negotiable)
|
|
141
|
+
Every add or change must keep `uv run pytest` green. A probe must read **slop on `references/vulnerable`,
|
|
142
|
+
clean on `references/hardened`, and N/A or clean on `references/minimal`**: the same surface, three
|
|
143
|
+
verdicts. If your probe needs a surface the references lack, add it (broken in `vulnerable`, defended
|
|
144
|
+
in `hardened`). So "add a probe" is usually three coupled edits:
|
|
145
|
+
|
|
146
|
+
1. the probe YAML,
|
|
147
|
+
2. the reference surface (if new),
|
|
148
|
+
3. the test assertion.
|
|
149
|
+
|
|
150
|
+
That coupling is the point: a probe that cannot separate defended from broken from absent does not
|
|
151
|
+
merge.
|
|
152
|
+
|
|
153
|
+
**The score:** `tests/test_pipeline.py` holds the **single authoritative** vulnerable-app score
|
|
154
|
+
(`assert report.slop_score == N`, plus an `axis_slop` decomposition assertion that must sum to it). A
|
|
155
|
+
probe that fires on `vulnerable` changes `N` by its (damped) penalty, so update it there, in **one**
|
|
156
|
+
place. `test_remote.py` and the docker tests assert *deployer-equivalence* (they equal the
|
|
157
|
+
SubprocessDeployer baseline), so they self-track and never need editing for a scoring change.
|
|
158
|
+
|
|
159
|
+
## Over time
|
|
160
|
+
Versioning follows semver on a quarterly cadence, with PR review and public-vs-hidden pool governance.
|
|
161
|
+
Hidden probes are authored the same way but live in the private hidden-probe catalog repo, never this
|
|
162
|
+
public one.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# The Shape of AI Era Web App Slop
|
|
2
|
+
|
|
3
|
+
### A black-box quality audit of 1,528 live hackathon web apps
|
|
4
|
+
|
|
5
|
+
**Reference release:** `2026.1` · **Instrument:** Sloptic (deduction-only black-box grader) · **Population:** live hackathon submissions
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## TL;DR
|
|
10
|
+
|
|
11
|
+
- On a corpus of **1,528** live web apps graded black-box, AI era "slop" is overwhelmingly **chronic**, not **acute**. Pervasive missing hygiene, not exploitable holes.
|
|
12
|
+
- **98%** ship no Content-Security-Policy, **42%** never rate limit a login, **70%** have a critical accessibility violation. But only **3.8%** expose any exploitable vulnerability, and **zero** of the 1,528 a remote code execution class.
|
|
13
|
+
- The acute danger has not disappeared. It **relocated behind authentication and vendor boundaries**: only **16.6%** of the apps with observed runtime traffic (176 of 1,063) even have an own injectable backend, so a black-box grader mostly cannot reach the dangerous surface.
|
|
14
|
+
- **AI-builder apps are measurably sloppier, and it is a performance tax.** Apps generated by Lovable carry a statistically significant slop premium (median **72 vs 49** hand-built, p = 1.1e-5) that is **entirely performance**, heavy, slow bundles, not weaker security.
|
|
15
|
+
- The overall score is smooth and unimodal (good for ranking), but the security axis is **bimodal**, two "header flat tax" values account for **70%** of it.
|
|
16
|
+
- The median app defends **~94%** of its worst case failure surface. Slop is the rare exception, not the rule, per app.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. The question
|
|
21
|
+
|
|
22
|
+
AI assisted building makes shipping a web app nearly free, and the volume of "looks done" submissions has exploded. The security research is blunt about the tradeoff. Veracode's 2025 report, testing over 100 models on 80 tasks, found that **45%** of AI generated code introduces an OWASP Top 10 vulnerability and that AI written code carries **2.74 times** more flaws than human written code, a pass rate that had not budged by early 2026. The Cloud Security Alliance put the share of AI generated solutions carrying a design flaw or known vulnerability at **62%**. Hackathons feel this directly: when any team can generate a polished UI in minutes, judges report the bar for "impressive" moving from "does it look done" to "does it hold up." So a natural question for anyone grading these apps at scale:
|
|
23
|
+
|
|
24
|
+
> When you look at a large population of real, deployed, AI era web apps from the outside, **what does the failure actually look like?** A field of exploitable vulnerabilities, or something else?
|
|
25
|
+
|
|
26
|
+
This report answers that from one instrument's black-box view of 1,528 apps.
|
|
27
|
+
|
|
28
|
+
## 2. The dataset
|
|
29
|
+
|
|
30
|
+
| property | value |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Apps ingested | 2,250 |
|
|
33
|
+
| Apps successfully graded | **1,528** |
|
|
34
|
+
| Attrition | dead URLs, pages that are not web apps, and submissions that never deployed |
|
|
35
|
+
| Each row | one deployed app, graded over HTTP with no source and no spec |
|
|
36
|
+
| Grader version | frozen catalog of 91 probes across 3 axes |
|
|
37
|
+
|
|
38
|
+
Rows that could not be graded (a URL that never answered, a repo that never deployed) are excluded rather than scored, so the population is "apps that actually presented a working surface."
|
|
39
|
+
|
|
40
|
+
### 2.1 Where the apps came from
|
|
41
|
+
|
|
42
|
+
The corpus is drawn from **60 hackathons** on Devpost, whose public project galleries were scraped for submissions that shipped a live URL. The events span North America (the majority, largely US and Canadian university hackathons), Europe (London, Barcelona, Ireland), Latin America (Monterrey), and Asia Pacific (Singapore and Australia), with editions concentrated in 2025 and 2026. From these galleries, **2,250** submissions carried a gradeable URL and **1,528** graded successfully. The full list of 60 events is in Appendix A.
|
|
43
|
+
|
|
44
|
+
This provenance matters for reading the results. The population is young, collegiate, time boxed (a hackathon is a day or a weekend), and built in the AI era, so it is a clean look at what teams ship when speed is everything and the tooling writes much of the code. It is deliberately **not** a sample of production software.
|
|
45
|
+
|
|
46
|
+
Every figure in this report is computed on one grading run (n = 1,528). The frozen percentile curve used to rank a new app against this population remains **2026.1**, built from the prior run; it stays valid because the two runs rank correlate at **0.967** with an unchanged median, so the scored numbers here and the ranking curve describe the same population.
|
|
47
|
+
|
|
48
|
+
## 3. Method
|
|
49
|
+
|
|
50
|
+
Sloptic is a **black-box** grader. It reads no source, needs no spec, and emits one **slop score**: deduction-only, unbounded, lower is better, `0` means nothing was found. The score decomposes into three axes (security, quality, performance) whose subtotals sum exactly to the total. Penalties are risk priced (frequency times severity) and damped, so one root cause counts once.
|
|
51
|
+
|
|
52
|
+
Because it ignores the stack, the same 91 probes run identically against every app, which is what makes 1,528 unrelated apps **comparable on one axis**. Every grade also ships a coverage report, so a `0` that means "clean" is distinguishable from a `0` that means "we could not reach the surface."
|
|
53
|
+
|
|
54
|
+
Two things this method is honest about up front: it grades the **unauthenticated, observable** surface, and it measures **intent-independent** failures only (defects no matter what the app is for).
|
|
55
|
+
|
|
56
|
+
## 4. Results
|
|
57
|
+
|
|
58
|
+
### 4.1 The score distribution
|
|
59
|
+
|
|
60
|
+
| statistic | value |
|
|
61
|
+
|---|---|
|
|
62
|
+
| mean | 55.1 |
|
|
63
|
+
| median | 49 |
|
|
64
|
+
| max | 168 |
|
|
65
|
+
| distinct values | 132 |
|
|
66
|
+
| landmarks (p10 / p25 / p50 / p75 / p90 / p99) | 26 / 35 / 49 / 70 / 89 / 142 |
|
|
67
|
+
|
|
68
|
+
The overall score is **smooth and nearly unimodal**, the largest single spike holds under 5% of the population. This is exactly the property a ranking needs: the convolution of three axes and many probes spreads apps out, so a percentile is meaningful.
|
|
69
|
+
|
|
70
|
+
### 4.2 What drives the score
|
|
71
|
+
|
|
72
|
+
Penalty mass splits across the three axes like this:
|
|
73
|
+
|
|
74
|
+
| axis | share of total slop | median | shape |
|
|
75
|
+
|---|---:|---:|---|
|
|
76
|
+
| security | **41.9%** | 14 | bimodal (two values = 70%) |
|
|
77
|
+
| quality | 36.4% | 20 | spike at 0 (15%), then spread |
|
|
78
|
+
| performance | 21.7% | 0 | **62% score zero** |
|
|
79
|
+
|
|
80
|
+
The probes that fire most often are not exotic. They are missing HTTP headers:
|
|
81
|
+
|
|
82
|
+
| prevalence | probe |
|
|
83
|
+
|---:|---|
|
|
84
|
+
| 98% | missing Content-Security-Policy |
|
|
85
|
+
| 97% | no clickjacking defense |
|
|
86
|
+
| 91% | missing `X-Content-Type-Options` |
|
|
87
|
+
| 90% | missing `Referrer-Policy` |
|
|
88
|
+
| 70% | critical accessibility violation |
|
|
89
|
+
| 42% | login with no rate limiting |
|
|
90
|
+
|
|
91
|
+
A finding at 98% prevalence is nearly a constant, it taxes everyone and separates no one. The **discriminating** signal lives in the middle band (rate limiting at 42%, source map disclosure at ~10%, accessibility tiers, Core Web Vitals), which is where apps actually pull apart.
|
|
92
|
+
|
|
93
|
+
### 4.3 Severity composition: chronic, not acute
|
|
94
|
+
|
|
95
|
+
This is the headline. Split every security finding into **acute** (exploitable now) versus **chronic** (missing mitigation), and the population is lopsided:
|
|
96
|
+
|
|
97
|
+
| tier | rate | examples |
|
|
98
|
+
|---|---:|---|
|
|
99
|
+
| any acute finding | **3.8%** | |
|
|
100
|
+
| remote code execution / injection | **0%** | zero of 1,528 apps (SQLi / command / template injection) |
|
|
101
|
+
| data exposure | 2.68% | world readable managed backend, bulk PII |
|
|
102
|
+
| secret or source file leak | 0.85% | served `.env`, `.git`, backups, keys |
|
|
103
|
+
| source map disclosure (moderate) | ~10% | production `.map` leaks original source |
|
|
104
|
+
| chronic hygiene (representative) | 41 to 98% | headers, rate limiting, accessibility |
|
|
105
|
+
|
|
106
|
+
Read the two ends together: **~4% of apps are exploitable, not one has a remote code execution hole, while 40 to 98% are missing basic hygiene.** The functionality is mostly there; the nonfunctional floor is pervasively absent. That is the empirical signature of AI era slop from the outside, chronic rot, not a field of smoking guns.
|
|
107
|
+
|
|
108
|
+
### 4.4 Why the acute surface is so thin
|
|
109
|
+
|
|
110
|
+
The low acute rate is not "these apps are safe." It is an artifact of **where the modern stack puts the danger.** Of the graded apps, **1,063 exposed observed runtime traffic** we could classify by host tier. An app's traffic routinely spans several tiers at once (a same-origin API *and* a managed BaaS *and* a consumed vendor), so **the tiers OVERLAP: each row counts how many of the 1,063 have *any* host of that tier, not a slice of a pie. They sum to 1,487 tier-memberships across 1,063 apps (≈140%), not to 100%, and must not be read as shares of a whole.**
|
|
111
|
+
|
|
112
|
+
| host tier | apps with this tier (of 1,063 classified; overlapping) | injectable black-box? |
|
|
113
|
+
|---|---:|---|
|
|
114
|
+
| same-origin (static frontend) | 682 (64%) | no backend to inject |
|
|
115
|
+
| third-party vendor (consumed) | 272 (26%) | not the app's surface |
|
|
116
|
+
| opaque (unattributable off-origin) | 203 (19%) | not probed (flagged, no clean-bill credit) |
|
|
117
|
+
| **own backend (attributed)** | **176 (16.6%)** | **yes** |
|
|
118
|
+
| managed backend (Supabase / Firebase) | 154 (14%) | only via row-level security config |
|
|
119
|
+
|
|
120
|
+
**Only 16.6% of the classified apps (176 of 1,063 with observed runtime traffic) have an own injectable backend**, and that is already a share of the traffic-bearing subset, not of the full corpus. You cannot inject SQL into a static site, and a managed backend app's only misconfiguration knob is row-level security. So the classic acute classes (SQLi, RCE) have almost no surface to land on, and the genuine acute risk that remains is disproportionately **backend misconfiguration** (the data-exposure tier), which is exactly where a black-box probe can still reach it. (Tier counts are off-score diagnostics from `scripts/stats.py`; they are the overlapping tiers over the 1,063 classified population.)
|
|
121
|
+
|
|
122
|
+
By hosting platform, identified from response headers and origin suffix (`platform_id`, off-score), the population is dominated by Vercel:
|
|
123
|
+
|
|
124
|
+
| platform | apps (of 1,528) |
|
|
125
|
+
|---|---:|
|
|
126
|
+
| Vercel | 988 (65%) |
|
|
127
|
+
| unknown / custom (unattributable) | 136 (9%) |
|
|
128
|
+
| Netlify | 99 (6%) |
|
|
129
|
+
| GitHub Pages | 71 (5%) |
|
|
130
|
+
| Streamlit | 66 (4%) |
|
|
131
|
+
| Lovable | 56 (4%) |
|
|
132
|
+
| other (Render, Firebase, Cloud Run, Cloudflare, Railway, ...) | 112 (7%) |
|
|
133
|
+
|
|
134
|
+
A further 200 apps sit behind a Cloudflare edge and 70 behind Fastly; a fronting CDN can mask the origin platform, which is why 9% stay unattributable rather than guessed. Reading response headers as well as the domain suffix cuts that unattributable share to 9% (a suffix-only pass leaves ~32% "custom/other"). A population this heavy on Vercel also explains why bot challenges rarely interfered: Vercel's bot protection is opt-in, and hackathon teams almost never configure it.
|
|
135
|
+
|
|
136
|
+
### 4.5 The AI-builder slop premium
|
|
137
|
+
|
|
138
|
+
Because `platform_id` also fingerprints the *builder* from served markup (Lovable ships `cdn.gpteng.co`, Bolt its own signature), we can test the question the AI-slop thesis rests on: do AI-built apps carry more slop than hand-deployed ones? For Lovable the answer is yes, and it is specific.
|
|
139
|
+
|
|
140
|
+
| group | n | median slop | mean slop |
|
|
141
|
+
|---|---:|---:|---:|
|
|
142
|
+
| hand-built (no builder signature) | 1,459 | 49 | 54 |
|
|
143
|
+
| **Lovable** | **58** | **72** | **73** |
|
|
144
|
+
| Bolt | 11 | 44 | (n too small to read) |
|
|
145
|
+
|
|
146
|
+
The Lovable gap is statistically significant (Mann-Whitney U, one-sided, **p = 1.1e-5**) and it is **entirely performance**. Decomposed by axis (mean penalty per app), Lovable's performance slop is **30.0 versus 11.1** for hand-built (a +18.9 gap), while its security is marginally *lower* (−2.8) and its QA marginally higher (+2.6). Lovable apps are not less secure; they ship **heavy and slow**: large unsplit bundles, uncompressed responses, weak Core Web Vitals, the diffuse performance floor an AI builder trades away for generation speed.
|
|
147
|
+
|
|
148
|
+
Three bounds on the claim, stated. It is a correlation, not a controlled experiment, though the all-performance decomposition makes the mechanism concrete. Bolt's n = 11 is too small to read (its median is *lower*, but on eleven apps that is noise). And detection is a floor: a Lovable app on a custom domain with its signature stripped falls into the hand-built bucket, which can only *understate* the gap. The same pattern shows up by host: Streamlit is the sloppiest platform (median 88, another auto-generation stack), GitHub Pages the cleanest (34, static sites have little floor to miss).
|
|
149
|
+
|
|
150
|
+
### 4.6 Measurement validity
|
|
151
|
+
|
|
152
|
+
A ranking is only trustworthy if the ruler is stable. Two independent runs over the full corpus agree closely:
|
|
153
|
+
|
|
154
|
+
| metric | value |
|
|
155
|
+
|---|---:|
|
|
156
|
+
| Spearman rank correlation | **0.974** |
|
|
157
|
+
| deciles identical | 92.6% (96% within one) |
|
|
158
|
+
| apps scoring exactly the same | 92% |
|
|
159
|
+
| systematic drift | none (mean delta +0.15) |
|
|
160
|
+
|
|
161
|
+
The residual movement is confined to the probes where black-box nondeterminism is unavoidable (stateful browser behavior, Core Web Vitals timing, and the security tail behind authentication); the deterministic surface holds.
|
|
162
|
+
|
|
163
|
+
Coverage is reported per grade: the median app had **62%** of the battery apply to it, so the scores are legible as "clean," not "untested."
|
|
164
|
+
|
|
165
|
+
## 5. Interpretation
|
|
166
|
+
|
|
167
|
+
One number captures the thesis. The median app's **worst case slop** (the score it would carry if every applicable probe fired) is **866**, while its actual median score is **49**. The median app therefore realizes only about **6%** of its potential failure surface: it defends nearly everything it exposes, and fails on the diffuse hygiene it never thought about.
|
|
168
|
+
|
|
169
|
+
So the story of this corpus is not "AI writes insecure code that gets exploited." From the black-box, it is "AI writes **functional** code that ships without the boring, universal, nonfunctional floor," no headers, no rate limiting, no accessibility, sometimes a leaked source map. The acute danger is real but rare and increasingly hidden behind authentication and vendor APIs, which is a finding about the modern stack as much as about the apps.
|
|
170
|
+
|
|
171
|
+
## 6. Limitations (stated, not hidden)
|
|
172
|
+
|
|
173
|
+
- **Unauthenticated surface only.** Defects behind a login the grader cannot establish are undercounted. The true acute rate is a floor.
|
|
174
|
+
- **Recall is not audited, and precision is vouched, not blanket.** This release guarantees **stability** (the ruler repeats) and **precision on the classes that carry explicit precision rules**; findings elsewhere are **unaudited, not endorsed**. That unaudited mass (most of the in-score penalty by count) is dominated by **deterministic presence checks** (a security header is absent, a `.map` resolves, a control is dead) where false-positive risk is structurally low. But the audit cannot distinguish "no rule needed" from "no rule written," and we can, so we report it as unaudited rather than claim a precision we have not checked. The false-negative rate against ground truth is measured separately and is ongoing: a low finding rate for a class could mean "rare" or "our detector missed it," and only a recall benchmark distinguishes them.
|
|
175
|
+
- **Intent-independent scope.** Sloptic grades the universal floor, not whether a feature is good. Originality, product quality, and creativity are out of scope by design.
|
|
176
|
+
- **Population, not universe.** Hackathon submissions skew toward young, small apps that are heavy on the frontend. The distribution should not be read as representative of production software at large.
|
|
177
|
+
|
|
178
|
+
## 7. Reproduce
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
# freeze the reference distribution from a corpus run
|
|
182
|
+
uv run python scripts/benchmark.py build <run>.jsonl --version 2026.1 --status final
|
|
183
|
+
|
|
184
|
+
# place any single app on that curve
|
|
185
|
+
uv run python -m sloptic.cli --target https://your-app.example.com --out app.jsonl
|
|
186
|
+
uv run python scripts/benchmark.py rank --results app.jsonl
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Sources
|
|
190
|
+
|
|
191
|
+
Background figures on AI generated code are external; the corpus figures are this instrument's own measurements over the 2026.1 population.
|
|
192
|
+
|
|
193
|
+
- [Veracode 2025 GenAI Code Security Report](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/) (45% of AI generated code introduces an OWASP Top 10 flaw; 2.74x more vulnerabilities than human written code)
|
|
194
|
+
- [Veracode, Spring 2026 GenAI Code Security update](https://www.veracode.com/blog/spring-2026-genai-code-security/) (the 45% pass rate had not improved through early 2026)
|
|
195
|
+
- [Cloud Security Alliance, AI generated code vulnerability research](https://labs.cloudsecurityalliance.org/research/csa-research-note-ai-codegen-vulnerability-debt-20260406-csa/) (62% carry a design flaw or known vulnerability)
|
|
196
|
+
- [Vercel Bot Management docs](https://vercel.com/docs/bot-management) (bot protection is opt-in, which is why it rarely interfered)
|
|
197
|
+
|
|
198
|
+
## Appendix A: the 60 hackathons
|
|
199
|
+
|
|
200
|
+
Devpost event slugs, as ingested:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
hack-brown-2026 ds-x bigred-hacks-2025 luddyhacks
|
|
204
|
+
innovation-hacks-2 hackgt-12 hacktech-by-caltech-2026 mhacks-2025
|
|
205
|
+
hacknyu-2025 vthacks-13 jumbohack-2025 devfest-2026
|
|
206
|
+
hack-mit-2023 ai-hackathon-2026 hacktx2025 jumbohack-2026
|
|
207
|
+
hackrice-15 hackprinceton-fall-2025 hackdartmouth-xi hackbeanpot2025
|
|
208
|
+
la-hacks-2026 la-hacks-2025 treehacks-2026 bostonhacks-2025
|
|
209
|
+
hackharvard-2025 hackduke-code-for-good-2026 hackillinois-2026 hackcwru-012025
|
|
210
|
+
beaverhacks boilermake-xii terrahacks-2025 hackpsu-spring-2026
|
|
211
|
+
uwb-hacks-the-future civic-hacks-2026 hackumass-xiii uofthacks-13
|
|
212
|
+
deltahacks-12 hack-western-12 nwhacks-2026 hackku26
|
|
213
|
+
newhacks-2025 hacknc-2025 hacklondon-2026 hackupc-2026
|
|
214
|
+
kenthackit emory-hacks-2025-fall interhackbcn steminate-hacks-2026
|
|
215
|
+
hackeurope hack4her-mty hackmty2025 hacknroll2026
|
|
216
|
+
uncommon-hacks-2026 swamphacks-xi hack-arizona-2026 wildhacks-2026
|
|
217
|
+
nus-fintech-summit-2026 unihack2026 usaii-global-ai-hackathon-2026 hack-ireland-2025
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
*All figures are aggregate over the 2026.1 population. No per app identities are stored or reported.*
|
sloptic-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Ian Sun
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|