@wazir-dev/cli 1.0.0
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.
- package/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
# Authentication & Authorization Anti-Patterns
|
|
2
|
+
> A catalog of authentication and authorization mistakes that lead to account takeovers, data breaches,
|
|
3
|
+
> and privilege escalation. Each entry documents the pattern, its real-world consequences, and concrete fixes.
|
|
4
|
+
> Broken Access Control is the #1 vulnerability in the OWASP Top 10 (2021 and 2025), found in 94% of
|
|
5
|
+
> applications tested. These anti-patterns are not theoretical -- they are extracted from breaches,
|
|
6
|
+
> CVEs, and bug bounty reports spanning the last fifteen years.
|
|
7
|
+
> **Domain:** Backend
|
|
8
|
+
> **Anti-patterns covered:** 20
|
|
9
|
+
> **Highest severity:** Critical
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Anti-Patterns
|
|
14
|
+
|
|
15
|
+
### AP-01: Rolling Your Own Crypto
|
|
16
|
+
|
|
17
|
+
**Also known as:** Homebrew authentication, DIY encryption, custom token scheme
|
|
18
|
+
**Frequency:** Common
|
|
19
|
+
**Severity:** Critical
|
|
20
|
+
**Detection difficulty:** Hard
|
|
21
|
+
|
|
22
|
+
**What it looks like:**
|
|
23
|
+
```python
|
|
24
|
+
import hashlib, time, base64
|
|
25
|
+
|
|
26
|
+
def generate_token(user_id):
|
|
27
|
+
raw = f"{user_id}:{time.time()}:{MY_APP_SECRET}"
|
|
28
|
+
return base64.b64encode(hashlib.sha256(raw.encode()).digest()).decode()
|
|
29
|
+
|
|
30
|
+
def verify_token(token, user_id):
|
|
31
|
+
# "We'll just regenerate and compare"
|
|
32
|
+
# But time.time() has moved on, so this never actually works
|
|
33
|
+
# Developer adds a 60-second window... then 5 minutes... then gives up and skips verification
|
|
34
|
+
return True
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Why developers do it:**
|
|
38
|
+
They want full control, consider standard libraries "overkill" for their use case, or do not understand the complexity beneath JWT/OAuth/session libraries. Sometimes the motivation is avoiding a dependency.
|
|
39
|
+
|
|
40
|
+
**What goes wrong:**
|
|
41
|
+
Custom schemes lack peer review, formal verification, and adversarial testing. The CSL Dualcom CS2300-R alarm system used a hybrid roll-your-own crypto implementation that researchers broke trivially, putting physical security of shops and offices at risk. Research from a 2021 empirical study found that 37.2% of vulnerabilities in cryptographic libraries are memory safety issues, not even cryptographic ones -- homebrew implementations inherit both classes of bugs. AES-GCM nonce reuse, predictable IVs, and wrong-mode-of-operation errors are the most common categories.
|
|
42
|
+
|
|
43
|
+
**The fix:**
|
|
44
|
+
```python
|
|
45
|
+
# Use established libraries: PyJWT, authlib, passlib, or your framework's auth module
|
|
46
|
+
import jwt
|
|
47
|
+
|
|
48
|
+
def generate_token(user_id):
|
|
49
|
+
return jwt.encode(
|
|
50
|
+
{"sub": user_id, "exp": datetime.utcnow() + timedelta(hours=1)},
|
|
51
|
+
settings.SECRET_KEY,
|
|
52
|
+
algorithm="HS256"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def verify_token(token):
|
|
56
|
+
return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Detection rule:**
|
|
60
|
+
Flag any import of `hashlib` or `hmac` used in conjunction with session/token generation logic. Flag custom `encrypt`/`decrypt` functions that do not delegate to established libraries (libsodium, OpenSSL wrappers, AWS KMS).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### AP-02: Plaintext or Weak Password Hashing
|
|
65
|
+
|
|
66
|
+
**Also known as:** MD5 passwords, unsalted hashes, reversible encryption for passwords
|
|
67
|
+
**Frequency:** Very Common
|
|
68
|
+
**Severity:** Critical
|
|
69
|
+
**Detection difficulty:** Easy
|
|
70
|
+
|
|
71
|
+
**What it looks like:**
|
|
72
|
+
```python
|
|
73
|
+
# Plaintext
|
|
74
|
+
def store_password(user, password):
|
|
75
|
+
db.execute("INSERT INTO users (email, password) VALUES (?, ?)", user.email, password)
|
|
76
|
+
|
|
77
|
+
# Weak hash
|
|
78
|
+
import hashlib
|
|
79
|
+
hashed = hashlib.md5(password.encode()).hexdigest()
|
|
80
|
+
|
|
81
|
+
# Unsalted SHA1
|
|
82
|
+
hashed = hashlib.sha1(password.encode()).hexdigest()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Why developers do it:**
|
|
86
|
+
Speed of implementation, unfamiliarity with password hashing algorithms, or the assumption that "hashing is hashing." Some developers confuse message-digest functions (MD5, SHA1) with password hashing functions (bcrypt, scrypt, Argon2).
|
|
87
|
+
|
|
88
|
+
**What goes wrong:**
|
|
89
|
+
- **RockYou (2009):** 32 million passwords stored in plaintext. The leaked dataset became the foundation of the `rockyou.txt` wordlist used in virtually every password cracking operation since.
|
|
90
|
+
- **LinkedIn (2012):** 6.5 million passwords hashed with unsalted SHA1, cracked within hours using rainbow tables. The full dump later revealed 117 million credentials.
|
|
91
|
+
- **Adobe (2013):** 153 million accounts breached. Passwords were encrypted with 3DES in ECB mode (not hashed), and plaintext hints were stored alongside them. Identical passwords produced identical ciphertexts, enabling mass decryption via frequency analysis.
|
|
92
|
+
- **Yahoo (2014):** Used MD5 for password hashing before migrating to bcrypt.
|
|
93
|
+
|
|
94
|
+
A modern GPU can compute 10 billion MD5 hashes per second and hundreds of millions of SHA1 hashes per second.
|
|
95
|
+
|
|
96
|
+
**The fix:**
|
|
97
|
+
```python
|
|
98
|
+
# Use bcrypt (or Argon2id for new projects)
|
|
99
|
+
from passlib.hash import bcrypt
|
|
100
|
+
|
|
101
|
+
def store_password(password):
|
|
102
|
+
return bcrypt.using(rounds=12).hash(password)
|
|
103
|
+
|
|
104
|
+
def verify_password(password, stored_hash):
|
|
105
|
+
return bcrypt.verify(password, stored_hash)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Detection rule:**
|
|
109
|
+
Grep for `hashlib.md5`, `hashlib.sha1`, `hashlib.sha256` adjacent to `password` variables. Flag any `INSERT` statement where a password column receives an unhashed value. Flag absence of bcrypt/scrypt/argon2 in dependency manifests.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### AP-03: JWT Stored in localStorage
|
|
114
|
+
|
|
115
|
+
**Also known as:** XSS-accessible tokens, client-side token exposure
|
|
116
|
+
**Frequency:** Very Common
|
|
117
|
+
**Severity:** High
|
|
118
|
+
**Detection difficulty:** Easy
|
|
119
|
+
|
|
120
|
+
**What it looks like:**
|
|
121
|
+
```javascript
|
|
122
|
+
// After login
|
|
123
|
+
fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) })
|
|
124
|
+
.then(res => res.json())
|
|
125
|
+
.then(data => {
|
|
126
|
+
localStorage.setItem('token', data.jwt); // Accessible to any JS on the page
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// On every request
|
|
130
|
+
fetch('/api/data', {
|
|
131
|
+
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Why developers do it:**
|
|
136
|
+
SPA tutorials universally demonstrate this pattern. localStorage is persistent across tabs, survives page reloads, and the API is trivial. Developers are unaware that any JavaScript running on the page (including XSS payloads, compromised npm packages, or injected analytics scripts) can read localStorage.
|
|
137
|
+
|
|
138
|
+
**What goes wrong:**
|
|
139
|
+
A single XSS vulnerability allows an attacker to exfiltrate every token in localStorage. Unlike httpOnly cookies, there is no browser-level protection. The token can be sent to an attacker-controlled server and replayed from any location. The blast radius expands if the JWT has a long lifetime (see AP-16).
|
|
140
|
+
|
|
141
|
+
**The fix:**
|
|
142
|
+
```javascript
|
|
143
|
+
// Server sets the token as an httpOnly, Secure, SameSite cookie
|
|
144
|
+
// Response from POST /api/login:
|
|
145
|
+
// Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
|
|
146
|
+
|
|
147
|
+
// Client code needs no token handling -- the browser sends the cookie automatically
|
|
148
|
+
fetch('/api/data', { credentials: 'include' });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If you must use a bearer token (e.g., cross-origin API), store it in a JavaScript closure or in-memory variable, and accept that it will not survive page reloads.
|
|
152
|
+
|
|
153
|
+
**Detection rule:**
|
|
154
|
+
Grep for `localStorage.setItem` or `sessionStorage.setItem` with token/jwt/auth keywords. Flag absence of `HttpOnly` and `Secure` flags on authentication cookies in `Set-Cookie` headers.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### AP-04: Not Validating JWT Signature or Expiration
|
|
159
|
+
|
|
160
|
+
**Also known as:** JWT bypass, `alg: none` attack, algorithm confusion
|
|
161
|
+
**Frequency:** Common
|
|
162
|
+
**Severity:** Critical
|
|
163
|
+
**Detection difficulty:** Moderate
|
|
164
|
+
|
|
165
|
+
**What it looks like:**
|
|
166
|
+
```python
|
|
167
|
+
# Decoding without verification
|
|
168
|
+
import base64, json
|
|
169
|
+
|
|
170
|
+
def get_user_from_token(token):
|
|
171
|
+
payload = token.split('.')[1]
|
|
172
|
+
payload += '=' * (4 - len(payload) % 4) # Fix padding
|
|
173
|
+
return json.loads(base64.urlsafe_b64decode(payload))
|
|
174
|
+
# No signature check. No expiration check. Attacker can forge any payload.
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# Accepting any algorithm the token claims
|
|
179
|
+
jwt.decode(token, secret, algorithms=None) # Will accept "none" algorithm
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Why developers do it:**
|
|
183
|
+
Developers treat JWTs as opaque session IDs rather than signed claims. Some copy decode-only snippets from Stack Overflow. Others configure JWT libraries permissively to "get it working" during development and never tighten the configuration.
|
|
184
|
+
|
|
185
|
+
**What goes wrong:**
|
|
186
|
+
- **`alg: none` attack:** Attacker sets the algorithm header to `"none"` and removes the signature. Early versions of many JWT libraries accepted this as valid. Auth0 disclosed critical vulnerabilities in multiple JWT libraries in 2015 enabling this exact attack.
|
|
187
|
+
- **Algorithm confusion (RS256 to HS256):** Attacker changes the algorithm from RS256 (asymmetric) to HS256 (symmetric) and signs the token with the server's public key (which is public). The server, configured to accept HS256, validates the signature using the public key as the HMAC secret. This was a widespread vulnerability class disclosed in 2015 affecting libraries across Node.js, PHP, Java, and Python.
|
|
188
|
+
- **CVE-2025-4692, CVE-2025-30144, CVE-2025-27371:** Critical JWT library bypass vulnerabilities affecting cloud platforms and SaaS stacks in 2025, exposing millions of users to remote code execution.
|
|
189
|
+
|
|
190
|
+
**The fix:**
|
|
191
|
+
```python
|
|
192
|
+
# Always specify allowed algorithms explicitly
|
|
193
|
+
import jwt
|
|
194
|
+
|
|
195
|
+
def verify_token(token):
|
|
196
|
+
return jwt.decode(
|
|
197
|
+
token,
|
|
198
|
+
settings.PUBLIC_KEY,
|
|
199
|
+
algorithms=["RS256"], # Whitelist algorithms
|
|
200
|
+
options={
|
|
201
|
+
"verify_exp": True, # Enforce expiration
|
|
202
|
+
"verify_aud": True, # Verify audience
|
|
203
|
+
"require": ["exp", "sub", "aud"] # Require mandatory claims
|
|
204
|
+
},
|
|
205
|
+
audience="my-api"
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Detection rule:**
|
|
210
|
+
Flag `jwt.decode` calls where `algorithms` is `None`, empty, or includes `"none"`. Flag JWT decode calls that do not verify the signature (`verify=False`, `options={"verify_signature": False}`). Flag absence of `exp` claim validation.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### AP-05: Missing Authorization Checks (IDOR)
|
|
215
|
+
|
|
216
|
+
**Also known as:** Insecure Direct Object Reference, horizontal privilege escalation, broken object-level authorization
|
|
217
|
+
**Frequency:** Very Common
|
|
218
|
+
**Severity:** Critical
|
|
219
|
+
**Detection difficulty:** Hard
|
|
220
|
+
|
|
221
|
+
**What it looks like:**
|
|
222
|
+
```python
|
|
223
|
+
# User can access any invoice by changing the ID
|
|
224
|
+
@app.route('/api/invoices/<invoice_id>')
|
|
225
|
+
@login_required
|
|
226
|
+
def get_invoice(invoice_id):
|
|
227
|
+
invoice = db.invoices.find_one({"_id": invoice_id})
|
|
228
|
+
return jsonify(invoice) # No check: does this invoice belong to the requesting user?
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
GET /api/invoices/1001 -> User's own invoice
|
|
233
|
+
GET /api/invoices/1002 -> Another user's invoice (returned without error)
|
|
234
|
+
GET /api/invoices/1003 -> Another user's invoice (returned without error)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Why developers do it:**
|
|
238
|
+
Authentication is treated as sufficient. Developers verify "is this a logged-in user?" but not "does this user have access to this specific resource?" This is especially common in CRUD generators and ORM-backed REST APIs where routes are auto-generated.
|
|
239
|
+
|
|
240
|
+
**What goes wrong:**
|
|
241
|
+
IDOR is the most common access control vulnerability in web applications.
|
|
242
|
+
- **Snapchat (2015):** Personal information exposed via IDOR when attackers manipulated user ID parameters in API requests.
|
|
243
|
+
- **Uber (2017):** Attackers manipulated price parameters in ride-booking API to modify fares.
|
|
244
|
+
- **US Department of Defense (2020):** A researcher discovered an IDOR that allowed changing passwords on DoD web servers.
|
|
245
|
+
- **PayPal:** IDOR allowed Business Account owners to assign secondary users from other accounts.
|
|
246
|
+
- **Vimeo:** IDOR in password reset allowed full account takeover.
|
|
247
|
+
|
|
248
|
+
OWASP found Broken Access Control in 94% of applications tested, with over 318,000 occurrences in their contributed dataset.
|
|
249
|
+
|
|
250
|
+
**The fix:**
|
|
251
|
+
```python
|
|
252
|
+
@app.route('/api/invoices/<invoice_id>')
|
|
253
|
+
@login_required
|
|
254
|
+
def get_invoice(invoice_id):
|
|
255
|
+
invoice = db.invoices.find_one({
|
|
256
|
+
"_id": invoice_id,
|
|
257
|
+
"owner_id": current_user.id # Scope query to the authenticated user
|
|
258
|
+
})
|
|
259
|
+
if not invoice:
|
|
260
|
+
abort(404) # Return 404, not 403 (don't reveal existence)
|
|
261
|
+
return jsonify(invoice)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Detection rule:**
|
|
265
|
+
Flag any database query in an authenticated endpoint that uses a user-supplied ID without including the authenticated user's ID in the query filter. Flag route handlers that accept resource IDs but do not reference `current_user`, `request.user`, or equivalent.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### AP-06: Client-Side Only Authorization
|
|
270
|
+
|
|
271
|
+
**Also known as:** UI-only access control, front-end gating, hidden button security
|
|
272
|
+
**Frequency:** Common
|
|
273
|
+
**Severity:** Critical
|
|
274
|
+
**Detection difficulty:** Moderate
|
|
275
|
+
|
|
276
|
+
**What it looks like:**
|
|
277
|
+
```javascript
|
|
278
|
+
// React component
|
|
279
|
+
function AdminPanel() {
|
|
280
|
+
const { user } = useAuth();
|
|
281
|
+
if (user.role !== 'admin') return <Redirect to="/dashboard" />;
|
|
282
|
+
|
|
283
|
+
return <AdminDashboard />;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// But the API has no role check:
|
|
287
|
+
app.get('/api/admin/users', authenticateToken, (req, res) => {
|
|
288
|
+
// Any authenticated user can call this endpoint directly
|
|
289
|
+
const users = await db.query('SELECT * FROM users');
|
|
290
|
+
res.json(users);
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Why developers do it:**
|
|
295
|
+
The developer sees the admin panel hidden in the UI and assumes the protection is complete. In SPA architectures, it is natural to gate routes on the client. The server-side check is forgotten or deferred.
|
|
296
|
+
|
|
297
|
+
**What goes wrong:**
|
|
298
|
+
Any user with a valid token can call the admin API directly using curl, Postman, or browser dev tools. CWE-602 (Client-Side Enforcement of Server-Side Security) and CWE-603 (Use of Client-Side Authentication) document this pattern formally. OWASP Mobile Top 10 ranks Insecure Authentication/Authorization as M3, noting that "developers should assume all client-side authorization controls can be bypassed."
|
|
299
|
+
|
|
300
|
+
A 2026 write-up demonstrated how applications returning user context objects containing role and permission fields allowed privilege escalation through simple request manipulation when the backend consumed those fields directly without server-side recalculation.
|
|
301
|
+
|
|
302
|
+
**The fix:**
|
|
303
|
+
```javascript
|
|
304
|
+
// Server-side middleware enforces authorization
|
|
305
|
+
function requireRole(role) {
|
|
306
|
+
return (req, res, next) => {
|
|
307
|
+
// Role comes from the server session/token, NOT from the request body
|
|
308
|
+
if (req.user.role !== role) {
|
|
309
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
310
|
+
}
|
|
311
|
+
next();
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
app.get('/api/admin/users', authenticateToken, requireRole('admin'), async (req, res) => {
|
|
316
|
+
const users = await db.query('SELECT * FROM users');
|
|
317
|
+
res.json(users);
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Detection rule:**
|
|
322
|
+
Flag API routes that lack authorization middleware. Compare the set of front-end route guards against server-side middleware to find gaps. Flag any endpoint under `/admin` or `/internal` paths that only has authentication (not authorization) middleware.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### AP-07: Hardcoded Credentials and API Keys
|
|
327
|
+
|
|
328
|
+
**Also known as:** Committed secrets, embedded credentials, config-in-code
|
|
329
|
+
**Frequency:** Very Common
|
|
330
|
+
**Severity:** Critical
|
|
331
|
+
**Detection difficulty:** Easy
|
|
332
|
+
|
|
333
|
+
**What it looks like:**
|
|
334
|
+
```python
|
|
335
|
+
# In source code
|
|
336
|
+
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
|
|
337
|
+
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
338
|
+
DATABASE_URL = "postgresql://admin:P@ssw0rd123@prod-db.internal:5432/app"
|
|
339
|
+
|
|
340
|
+
# In a config file checked into git
|
|
341
|
+
# config/settings.py
|
|
342
|
+
STRIPE_SECRET_KEY = "REDACTED_STRIPE_KEY"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Why developers do it:**
|
|
346
|
+
It is the fastest path from "not working" to "working." Environment variables and secret managers add setup friction. Developers intend to move secrets out "later" and forget or deprioritize it.
|
|
347
|
+
|
|
348
|
+
**What goes wrong:**
|
|
349
|
+
- **Uber (2016):** Hackers found AWS credentials on a private GitHub repo, used them to access an S3 bucket with data on 57 million customers and drivers.
|
|
350
|
+
- **GitHub (2023):** GitGuardian reported over 12.8 million secrets leaked in public GitHub repositories in a single year.
|
|
351
|
+
- **GitHub (2024):** 39 million API keys and credentials exposed across public repos. 91.6% of leaked secrets remained valid after five days. Only 2.6% were revoked within the first hour.
|
|
352
|
+
- **AI companies (2025):** 65% of 50 AI companies examined by Wiz had verified secrets leaked on GitHub, exposing models and training data.
|
|
353
|
+
|
|
354
|
+
Once committed, secrets persist in Git history even after deletion from the current branch.
|
|
355
|
+
|
|
356
|
+
**The fix:**
|
|
357
|
+
```python
|
|
358
|
+
# Use environment variables
|
|
359
|
+
import os
|
|
360
|
+
AWS_ACCESS_KEY = os.environ["AWS_ACCESS_KEY"]
|
|
361
|
+
|
|
362
|
+
# Or use a secret manager
|
|
363
|
+
from aws_secretsmanager import get_secret
|
|
364
|
+
db_password = get_secret("prod/db/password")
|
|
365
|
+
|
|
366
|
+
# Pre-commit hook to prevent commits
|
|
367
|
+
# .pre-commit-config.yaml
|
|
368
|
+
repos:
|
|
369
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
370
|
+
hooks:
|
|
371
|
+
- id: gitleaks
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Detection rule:**
|
|
375
|
+
Run gitleaks, truffleHog, or GitGuardian on every commit. Flag string literals matching known key patterns (AWS `AKIA*`, Stripe `sk_live_*`, GitHub `ghp_*`). Flag any variable named `password`, `secret`, `api_key`, or `token` assigned a string literal.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### AP-08: Session Fixation
|
|
380
|
+
|
|
381
|
+
**Also known as:** Pre-set session ID, session adoption
|
|
382
|
+
**Frequency:** Occasional
|
|
383
|
+
**Severity:** High
|
|
384
|
+
**Detection difficulty:** Moderate
|
|
385
|
+
|
|
386
|
+
**What it looks like:**
|
|
387
|
+
```python
|
|
388
|
+
# Login handler that does NOT regenerate the session ID
|
|
389
|
+
@app.route('/login', methods=['POST'])
|
|
390
|
+
def login():
|
|
391
|
+
user = authenticate(request.form['username'], request.form['password'])
|
|
392
|
+
if user:
|
|
393
|
+
session['user_id'] = user.id # Set user in EXISTING session
|
|
394
|
+
# The session ID in the cookie remains the same as before login
|
|
395
|
+
return redirect('/dashboard')
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Attack flow:
|
|
399
|
+
1. Attacker visits the site, receives session ID `abc123`.
|
|
400
|
+
2. Attacker sends victim a link: `https://app.com/login?sid=abc123`.
|
|
401
|
+
3. Victim logs in. Server associates `abc123` with the victim's account.
|
|
402
|
+
4. Attacker uses `abc123` to access the victim's authenticated session.
|
|
403
|
+
|
|
404
|
+
**Why developers do it:**
|
|
405
|
+
Session regeneration is not the default in many frameworks. Developers are unaware that the session ID is security-critical and must change at authentication boundaries.
|
|
406
|
+
|
|
407
|
+
**What goes wrong:**
|
|
408
|
+
- **Schneider Electric EcoStruxure PME:** A session fixation vulnerability allowed attackers to send a crafted link with a predefined session ID. When the victim logged in via that link, the attacker used the same session ID to hijack the authenticated session -- without needing credentials.
|
|
409
|
+
- This pattern has been found in routers, industrial control systems, and enterprise applications where session management is handled manually.
|
|
410
|
+
|
|
411
|
+
**The fix:**
|
|
412
|
+
```python
|
|
413
|
+
@app.route('/login', methods=['POST'])
|
|
414
|
+
def login():
|
|
415
|
+
user = authenticate(request.form['username'], request.form['password'])
|
|
416
|
+
if user:
|
|
417
|
+
# Regenerate session ID on authentication state change
|
|
418
|
+
session.regenerate() # Framework-specific: Flask-Session, Django, etc.
|
|
419
|
+
session['user_id'] = user.id
|
|
420
|
+
return redirect('/dashboard')
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
In Django this is automatic. In Express.js: `req.session.regenerate(callback)`. In Spring Security: `sessionFixationProtection="newSession"`.
|
|
424
|
+
|
|
425
|
+
**Detection rule:**
|
|
426
|
+
Flag login handlers that set session/user data without calling session regeneration. Search for `session['user']` or `req.session.user =` assignments not preceded by `session.regenerate()`, `session.invalidate()`, or equivalent.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### AP-09: Missing CSRF Protection
|
|
431
|
+
|
|
432
|
+
**Also known as:** Cross-site request forgery, session riding, one-click attack
|
|
433
|
+
**Frequency:** Common
|
|
434
|
+
**Severity:** High
|
|
435
|
+
**Detection difficulty:** Easy
|
|
436
|
+
|
|
437
|
+
**What it looks like:**
|
|
438
|
+
```html
|
|
439
|
+
<!-- Attacker's page at evil.com -->
|
|
440
|
+
<form action="https://bank.com/transfer" method="POST" id="exploit">
|
|
441
|
+
<input type="hidden" name="to" value="attacker-account" />
|
|
442
|
+
<input type="hidden" name="amount" value="10000" />
|
|
443
|
+
</form>
|
|
444
|
+
<script>document.getElementById('exploit').submit();</script>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
# Server has no CSRF token validation
|
|
449
|
+
@app.route('/transfer', methods=['POST'])
|
|
450
|
+
@login_required
|
|
451
|
+
def transfer():
|
|
452
|
+
# The browser sends the session cookie automatically
|
|
453
|
+
# No CSRF token is checked
|
|
454
|
+
process_transfer(request.form['to'], request.form['amount'])
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Why developers do it:**
|
|
458
|
+
API-first architectures using JSON and bearer tokens are naturally CSRF-resistant (the browser does not auto-attach bearer tokens). But when authentication uses cookies -- which is common and recommended for XSS protection (see AP-03) -- CSRF protection becomes mandatory. Developers switching from bearer-token to cookie-based auth forget this trade-off.
|
|
459
|
+
|
|
460
|
+
**What goes wrong:**
|
|
461
|
+
- **Tesla (2017):** A CSRF vulnerability in Tesla's web interface allowed forged requests to issue remote vehicle commands -- unlock doors, control climate systems. If a Tesla owner visited a malicious site while logged in, the attacker could silently control the vehicle.
|
|
462
|
+
- **Ubiquiti routers:** CSRF in the router admin interface allowed attackers to change DNS settings or push firmware if a user visited a malicious site while authenticated (which was almost always, since users rarely log out of router panels).
|
|
463
|
+
|
|
464
|
+
**The fix:**
|
|
465
|
+
```python
|
|
466
|
+
# Use framework CSRF middleware
|
|
467
|
+
from flask_wtf.csrf import CSRFProtect
|
|
468
|
+
csrf = CSRFProtect(app)
|
|
469
|
+
|
|
470
|
+
# In templates
|
|
471
|
+
<form method="post">
|
|
472
|
+
{{ csrf_token() }}
|
|
473
|
+
...
|
|
474
|
+
</form>
|
|
475
|
+
|
|
476
|
+
# For AJAX with cookies, use SameSite attribute
|
|
477
|
+
# Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Detection rule:**
|
|
481
|
+
Flag POST/PUT/DELETE endpoints that use cookie authentication but do not validate a CSRF token. Flag cookie `Set-Cookie` headers missing `SameSite=Strict` or `SameSite=Lax`. Flag forms without a hidden CSRF token field.
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
### AP-10: OAuth Misconfiguration
|
|
486
|
+
|
|
487
|
+
**Also known as:** Open redirect in OAuth, redirect URI bypass, state parameter omission
|
|
488
|
+
**Frequency:** Common
|
|
489
|
+
**Severity:** Critical
|
|
490
|
+
**Detection difficulty:** Moderate
|
|
491
|
+
|
|
492
|
+
**What it looks like:**
|
|
493
|
+
```python
|
|
494
|
+
# Accepting any redirect_uri
|
|
495
|
+
@app.route('/oauth/authorize')
|
|
496
|
+
def authorize():
|
|
497
|
+
redirect_uri = request.args.get('redirect_uri')
|
|
498
|
+
# No validation against a whitelist
|
|
499
|
+
code = generate_auth_code(request.user)
|
|
500
|
+
return redirect(f"{redirect_uri}?code={code}")
|
|
501
|
+
|
|
502
|
+
# Missing state parameter
|
|
503
|
+
def start_oauth():
|
|
504
|
+
return redirect(
|
|
505
|
+
f"https://provider.com/auth?client_id={CLIENT_ID}"
|
|
506
|
+
f"&redirect_uri={CALLBACK_URL}"
|
|
507
|
+
# No &state=<random> parameter -> CSRF on the OAuth flow
|
|
508
|
+
)
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Why developers do it:**
|
|
512
|
+
OAuth 2.0 is a framework, not a protocol -- it has many moving parts and the spec is permissive about implementation details. Developers follow "happy path" tutorials that skip security parameters. Wildcard redirect URIs are convenient during development.
|
|
513
|
+
|
|
514
|
+
**What goes wrong:**
|
|
515
|
+
- **Booking.com (2022):** Salt Labs found flaws in Booking.com's OAuth redirect URI handling that allowed attackers to redirect users to attacker-controlled domains and steal authorization codes.
|
|
516
|
+
- **Expo platform:** Researchers manipulated the `returnURL` parameter to redirect OAuth tokens to attacker-controlled domains, enabling account takeover on any app using Expo's OAuth.
|
|
517
|
+
- **Keycloak (CVE-2023-6927):** Bypass of redirect URI validation via wildcard matching, enabling authorization code theft in all Keycloak versions < 23.0.4.
|
|
518
|
+
- **Spring Security OAuth (CVE-2019-3778):** Open redirect vulnerability in versions prior to 2.3.6 leaking authorization codes.
|
|
519
|
+
- **Gradio (CVE-2026-28415):** Open redirect in OAuth login/logout endpoints in versions < 6.6.0.
|
|
520
|
+
|
|
521
|
+
**The fix:**
|
|
522
|
+
```python
|
|
523
|
+
ALLOWED_REDIRECT_URIS = {
|
|
524
|
+
"https://app.example.com/callback",
|
|
525
|
+
"https://app.example.com/oauth/callback",
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
@app.route('/oauth/authorize')
|
|
529
|
+
def authorize():
|
|
530
|
+
redirect_uri = request.args.get('redirect_uri')
|
|
531
|
+
if redirect_uri not in ALLOWED_REDIRECT_URIS:
|
|
532
|
+
abort(400, "Invalid redirect_uri")
|
|
533
|
+
|
|
534
|
+
state = generate_csrf_state()
|
|
535
|
+
session['oauth_state'] = state
|
|
536
|
+
# ... include state in the flow and validate on callback
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Always use exact-match redirect URI validation (no wildcards, no subdomain matching). Always validate the `state` parameter on callback. Use PKCE for public clients.
|
|
540
|
+
|
|
541
|
+
**Detection rule:**
|
|
542
|
+
Flag OAuth authorize endpoints that do not validate `redirect_uri` against a whitelist. Flag OAuth flows missing the `state` parameter. Flag redirect URI registrations using wildcards (`*`) or broad patterns.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### AP-11: Sequential and Guessable IDs for Access Control
|
|
547
|
+
|
|
548
|
+
**Also known as:** Enumerable identifiers, auto-increment exposure, predictable resource IDs
|
|
549
|
+
**Frequency:** Very Common
|
|
550
|
+
**Severity:** High
|
|
551
|
+
**Detection difficulty:** Easy
|
|
552
|
+
|
|
553
|
+
**What it looks like:**
|
|
554
|
+
```
|
|
555
|
+
GET /api/users/1001/profile
|
|
556
|
+
GET /api/users/1002/profile # Just increment
|
|
557
|
+
GET /api/users/1003/profile # Keep going
|
|
558
|
+
|
|
559
|
+
GET /api/documents/00000001
|
|
560
|
+
GET /api/documents/00000002 # Enumerate all documents
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
```python
|
|
564
|
+
# Database uses auto-increment primary keys exposed directly in URLs
|
|
565
|
+
class Invoice(db.Model):
|
|
566
|
+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Why developers do it:**
|
|
570
|
+
Auto-increment integer IDs are the default in every ORM and database. They are simple, indexable, and human-readable. Developers expose them in URLs because REST conventions encourage resource identification via URL path.
|
|
571
|
+
|
|
572
|
+
**What goes wrong:**
|
|
573
|
+
Sequential IDs make IDOR exploitation trivial. An attacker does not need to guess -- they just iterate. Combined with missing authorization checks (AP-05), this pattern enables automated scraping of entire databases. The Snapchat IDOR breach involved sequential user ID manipulation in API requests. Financial applications have been compromised via sequential transaction reference numbers.
|
|
574
|
+
|
|
575
|
+
Even with proper authorization, sequential IDs leak information: total user count, creation rate, and relative account age.
|
|
576
|
+
|
|
577
|
+
**The fix:**
|
|
578
|
+
```python
|
|
579
|
+
import uuid
|
|
580
|
+
|
|
581
|
+
class Invoice(db.Model):
|
|
582
|
+
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
583
|
+
# External ID: 550e8400-e29b-41d4-a716-446655440000
|
|
584
|
+
# Non-sequential, non-guessable
|
|
585
|
+
|
|
586
|
+
# For higher performance, use UUIDv7 (time-ordered but not sequential) or ULID
|
|
587
|
+
# Still enforce authorization checks -- UUIDs are defense-in-depth, not access control
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Detection rule:**
|
|
591
|
+
Flag API endpoints that expose integer IDs in URL paths where the backing model uses auto-increment. Flag endpoints accepting integer path parameters that lack authorization middleware.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### AP-12: Not Implementing Account Lockout
|
|
596
|
+
|
|
597
|
+
**Also known as:** Unlimited login attempts, missing brute-force protection, no rate limiting on login
|
|
598
|
+
**Frequency:** Common
|
|
599
|
+
**Severity:** High
|
|
600
|
+
**Detection difficulty:** Easy
|
|
601
|
+
|
|
602
|
+
**What it looks like:**
|
|
603
|
+
```python
|
|
604
|
+
@app.route('/login', methods=['POST'])
|
|
605
|
+
def login():
|
|
606
|
+
user = User.query.filter_by(email=request.form['email']).first()
|
|
607
|
+
if user and user.check_password(request.form['password']):
|
|
608
|
+
login_user(user)
|
|
609
|
+
return redirect('/dashboard')
|
|
610
|
+
return render_template('login.html', error='Invalid credentials')
|
|
611
|
+
# No attempt counting. No delay. No lockout. No CAPTCHA.
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Why developers do it:**
|
|
615
|
+
Account lockout adds complexity (lockout duration, unlock mechanisms, user communication). Developers fear locking out legitimate users. Rate limiting requires infrastructure (Redis, middleware). In early-stage products, security features are deprioritized.
|
|
616
|
+
|
|
617
|
+
**What goes wrong:**
|
|
618
|
+
Without rate limiting or lockout, attackers can attempt thousands of passwords per second via credential stuffing or brute force. OWASP API Security Top 10 (API2:2023) identifies Broken Authentication as a top risk, specifically noting that "anti-brute force mechanisms should be implemented and should be stricter than regular rate limiting." HackerOne reports document missing rate limits on login endpoints at Weblate and Acronis, among others.
|
|
619
|
+
|
|
620
|
+
**The fix:**
|
|
621
|
+
```python
|
|
622
|
+
from flask_limiter import Limiter
|
|
623
|
+
|
|
624
|
+
limiter = Limiter(app, key_func=get_remote_address)
|
|
625
|
+
|
|
626
|
+
@app.route('/login', methods=['POST'])
|
|
627
|
+
@limiter.limit("5 per minute") # Rate limit per IP
|
|
628
|
+
def login():
|
|
629
|
+
user = User.query.filter_by(email=request.form['email']).first()
|
|
630
|
+
if user:
|
|
631
|
+
if user.failed_attempts >= 5:
|
|
632
|
+
if user.locked_until and user.locked_until > datetime.utcnow():
|
|
633
|
+
abort(429, "Account temporarily locked")
|
|
634
|
+
user.failed_attempts = 0 # Reset after lockout period
|
|
635
|
+
|
|
636
|
+
if user.check_password(request.form['password']):
|
|
637
|
+
user.failed_attempts = 0
|
|
638
|
+
user.save()
|
|
639
|
+
login_user(user)
|
|
640
|
+
return redirect('/dashboard')
|
|
641
|
+
|
|
642
|
+
user.failed_attempts += 1
|
|
643
|
+
if user.failed_attempts >= 5:
|
|
644
|
+
user.locked_until = datetime.utcnow() + timedelta(minutes=15)
|
|
645
|
+
user.save()
|
|
646
|
+
|
|
647
|
+
# Generic error to prevent username enumeration
|
|
648
|
+
return render_template('login.html', error='Invalid credentials'), 401
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**Detection rule:**
|
|
652
|
+
Flag login endpoints that lack rate-limiting middleware. Flag authentication handlers that do not track or check failed attempt counts. Flag absence of rate-limiting libraries in project dependencies.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
### AP-13: Password Reset Token Reuse
|
|
657
|
+
|
|
658
|
+
**Also known as:** Non-expiring reset tokens, predictable reset tokens, reset link hijacking
|
|
659
|
+
**Frequency:** Occasional
|
|
660
|
+
**Severity:** Critical
|
|
661
|
+
**Detection difficulty:** Moderate
|
|
662
|
+
|
|
663
|
+
**What it looks like:**
|
|
664
|
+
```python
|
|
665
|
+
def request_password_reset(email):
|
|
666
|
+
user = User.query.filter_by(email=email).first()
|
|
667
|
+
# Token is MD5 of the email -- deterministic, reusable, predictable
|
|
668
|
+
token = hashlib.md5(email.encode()).hexdigest()
|
|
669
|
+
send_email(email, f"https://app.com/reset?token={token}")
|
|
670
|
+
|
|
671
|
+
def reset_password(token, new_password):
|
|
672
|
+
# Token is never invalidated after use
|
|
673
|
+
user = User.query.filter_by(reset_token=token).first()
|
|
674
|
+
user.password = hash_password(new_password)
|
|
675
|
+
user.save()
|
|
676
|
+
# reset_token is still valid -- attacker can use it again
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Why developers do it:**
|
|
680
|
+
Token generation and lifecycle management are treated as an afterthought. Developers generate a token, send it, and check it -- but forget to invalidate it on use, set an expiration, or use a cryptographically random generator.
|
|
681
|
+
|
|
682
|
+
**What goes wrong:**
|
|
683
|
+
- **Vikunja (CVE-2026-28268):** Password reset tokens could be reused indefinitely due to a failure to invalidate tokens upon use and a bug in the token cleanup cron job. An attacker who intercepted a single reset token could perform persistent account takeover at any point in the future.
|
|
684
|
+
- **Mavenlink (HackerOne):** The application generated password reset links based on the `Host` header, allowing attackers to redirect reset links to their domain and steal tokens.
|
|
685
|
+
- Applications using `MD5(email)` or `MD5(timestamp)` as reset tokens allow attackers who understand the generation mechanism to forge valid tokens for any account.
|
|
686
|
+
|
|
687
|
+
**The fix:**
|
|
688
|
+
```python
|
|
689
|
+
import secrets
|
|
690
|
+
from datetime import datetime, timedelta
|
|
691
|
+
|
|
692
|
+
def request_password_reset(email):
|
|
693
|
+
user = User.query.filter_by(email=email).first()
|
|
694
|
+
if not user:
|
|
695
|
+
return # Don't reveal whether the email exists
|
|
696
|
+
|
|
697
|
+
token = secrets.token_urlsafe(32) # Cryptographically random
|
|
698
|
+
user.reset_token = hash_token(token) # Store hashed, not raw
|
|
699
|
+
user.reset_token_expires = datetime.utcnow() + timedelta(hours=1)
|
|
700
|
+
user.save()
|
|
701
|
+
send_email(email, f"https://app.com/reset?token={token}")
|
|
702
|
+
|
|
703
|
+
def reset_password(token, new_password):
|
|
704
|
+
user = User.query.filter_by(reset_token=hash_token(token)).first()
|
|
705
|
+
if not user or user.reset_token_expires < datetime.utcnow():
|
|
706
|
+
abort(400, "Invalid or expired token")
|
|
707
|
+
|
|
708
|
+
user.password = hash_password(new_password)
|
|
709
|
+
user.reset_token = None # Invalidate immediately
|
|
710
|
+
user.reset_token_expires = None
|
|
711
|
+
user.save()
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Detection rule:**
|
|
715
|
+
Flag password reset handlers that do not nullify the token after use. Flag token generation using `md5`, `sha1`, or `time.time()` as sole entropy sources. Flag reset tokens without expiration timestamps.
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
### AP-14: Missing Multi-Factor Authentication for Sensitive Operations
|
|
720
|
+
|
|
721
|
+
**Also known as:** No MFA, single-factor-only for critical actions, missing step-up authentication
|
|
722
|
+
**Frequency:** Very Common
|
|
723
|
+
**Severity:** High
|
|
724
|
+
**Detection difficulty:** Easy
|
|
725
|
+
|
|
726
|
+
**What it looks like:**
|
|
727
|
+
```python
|
|
728
|
+
@app.route('/api/account/change-email', methods=['POST'])
|
|
729
|
+
@login_required
|
|
730
|
+
def change_email():
|
|
731
|
+
# Changes email (and thus password reset target) with no re-authentication
|
|
732
|
+
current_user.email = request.json['new_email']
|
|
733
|
+
current_user.save()
|
|
734
|
+
return jsonify({"status": "ok"})
|
|
735
|
+
|
|
736
|
+
@app.route('/api/transfers', methods=['POST'])
|
|
737
|
+
@login_required
|
|
738
|
+
def wire_transfer():
|
|
739
|
+
# Initiates a wire transfer with only a session cookie
|
|
740
|
+
process_wire(request.json)
|
|
741
|
+
return jsonify({"status": "ok"})
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Why developers do it:**
|
|
745
|
+
MFA adds user friction. Product teams push back on requiring re-authentication for "low-friction" user experience. Step-up authentication is architecturally complex -- it requires storing auth levels in the session and prompting conditionally.
|
|
746
|
+
|
|
747
|
+
**What goes wrong:**
|
|
748
|
+
- **Facebook (2018):** Attackers exploited the "View As" feature to steal access tokens for 50 million accounts. Without step-up authentication on sensitive operations, stolen tokens had full account access.
|
|
749
|
+
- **CircleCI (2023):** Malware on an engineer's laptop stole a valid, 2FA-backed SSO session. The session granted full access without re-authentication for sensitive operations.
|
|
750
|
+
|
|
751
|
+
Relying solely on password-based authentication means threat actors need only one credential -- which can be phished, purchased on the dark web, brute-forced, or guessed.
|
|
752
|
+
|
|
753
|
+
**The fix:**
|
|
754
|
+
```python
|
|
755
|
+
def require_recent_auth(max_age_seconds=300):
|
|
756
|
+
"""Require re-authentication for sensitive operations."""
|
|
757
|
+
def decorator(f):
|
|
758
|
+
@wraps(f)
|
|
759
|
+
def wrapper(*args, **kwargs):
|
|
760
|
+
last_auth = session.get('last_auth_time')
|
|
761
|
+
if not last_auth or (time.time() - last_auth) > max_age_seconds:
|
|
762
|
+
return jsonify({"error": "Re-authentication required"}), 403
|
|
763
|
+
return f(*args, **kwargs)
|
|
764
|
+
return wrapper
|
|
765
|
+
return decorator
|
|
766
|
+
|
|
767
|
+
@app.route('/api/account/change-email', methods=['POST'])
|
|
768
|
+
@login_required
|
|
769
|
+
@require_recent_auth(max_age_seconds=300) # Must have re-authed within 5 min
|
|
770
|
+
def change_email():
|
|
771
|
+
current_user.email = request.json['new_email']
|
|
772
|
+
current_user.save()
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Detection rule:**
|
|
776
|
+
Flag endpoints for password change, email change, phone change, MFA change, fund transfer, and API key generation that lack step-up authentication middleware.
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
### AP-15: Excessive Token Lifetimes
|
|
781
|
+
|
|
782
|
+
**Also known as:** Long-lived JWTs, tokens that never expire, week-long sessions
|
|
783
|
+
**Frequency:** Common
|
|
784
|
+
**Severity:** High
|
|
785
|
+
**Detection difficulty:** Easy
|
|
786
|
+
|
|
787
|
+
**What it looks like:**
|
|
788
|
+
```python
|
|
789
|
+
# Token valid for 30 days
|
|
790
|
+
token = jwt.encode({
|
|
791
|
+
"sub": user.id,
|
|
792
|
+
"exp": datetime.utcnow() + timedelta(days=30)
|
|
793
|
+
}, SECRET_KEY, algorithm="HS256")
|
|
794
|
+
|
|
795
|
+
# Or worse: no expiration at all
|
|
796
|
+
token = jwt.encode({"sub": user.id}, SECRET_KEY, algorithm="HS256")
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**Why developers do it:**
|
|
800
|
+
Long-lived tokens reduce the frequency of re-authentication, which improves user experience. Implementing token refresh flows adds complexity (refresh token storage, rotation, revocation). Developers default to "long enough that nobody complains."
|
|
801
|
+
|
|
802
|
+
**What goes wrong:**
|
|
803
|
+
A stolen token with a 30-day lifetime gives an attacker 30 days of access. With JWTs, there is no server-side revocation by default -- the token is valid until it expires. Organizations with proper token lifecycle management experience 47% fewer credential-compromise incidents according to SailPoint research.
|
|
804
|
+
|
|
805
|
+
Azure/Entra ID access tokens cannot be revoked once issued -- the only mitigation is short lifetimes. This architectural constraint has been exploited by attackers who steal tokens and maintain access even after the user changes their password.
|
|
806
|
+
|
|
807
|
+
**The fix:**
|
|
808
|
+
```python
|
|
809
|
+
# Short-lived access token + long-lived refresh token
|
|
810
|
+
access_token = jwt.encode({
|
|
811
|
+
"sub": user.id,
|
|
812
|
+
"exp": datetime.utcnow() + timedelta(minutes=15), # 15-minute access token
|
|
813
|
+
"type": "access"
|
|
814
|
+
}, SECRET_KEY, algorithm="HS256")
|
|
815
|
+
|
|
816
|
+
refresh_token = secrets.token_urlsafe(32)
|
|
817
|
+
# Store refresh token server-side (database), associated with the user
|
|
818
|
+
# Refresh tokens can be revoked by deleting the database entry
|
|
819
|
+
store_refresh_token(user.id, refresh_token, expires=timedelta(days=7))
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
**Detection rule:**
|
|
823
|
+
Flag JWT creation where `exp` exceeds 1 hour for access tokens. Flag JWTs without an `exp` claim. Flag refresh tokens with lifetimes exceeding 30 days.
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
### AP-16: Not Revoking Tokens on Password Change
|
|
828
|
+
|
|
829
|
+
**Also known as:** Stale sessions after credential change, zombie tokens, ghost access
|
|
830
|
+
**Frequency:** Common
|
|
831
|
+
**Severity:** High
|
|
832
|
+
**Detection difficulty:** Moderate
|
|
833
|
+
|
|
834
|
+
**What it looks like:**
|
|
835
|
+
```python
|
|
836
|
+
@app.route('/api/change-password', methods=['POST'])
|
|
837
|
+
@login_required
|
|
838
|
+
def change_password():
|
|
839
|
+
current_user.password = hash_password(request.json['new_password'])
|
|
840
|
+
current_user.save()
|
|
841
|
+
return jsonify({"status": "password changed"})
|
|
842
|
+
# All existing tokens, sessions, and refresh tokens remain valid
|
|
843
|
+
# If the password was changed because the account was compromised,
|
|
844
|
+
# the attacker still has access via existing tokens
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Why developers do it:**
|
|
848
|
+
Session invalidation requires tracking all active sessions/tokens per user. With stateless JWTs, server-side revocation requires maintaining a blocklist or version counter -- which undermines the "stateless" advantage. Developers implement the password change and consider it done.
|
|
849
|
+
|
|
850
|
+
**What goes wrong:**
|
|
851
|
+
- **Cloudflare (2023):** After the Okta breach, Cloudflare was attacked using tokens and service account credentials that had not been rotated post-compromise. The nation-state attacker maintained access because credentials from the prior breach were still valid.
|
|
852
|
+
- **General pattern:** Google explicitly revokes OAuth 2.0 tokens when a user's password changes (documented as a security feature). Many applications do not implement this, leaving a window where compromised sessions persist indefinitely.
|
|
853
|
+
|
|
854
|
+
A user who changes their password because they suspect compromise expects all other sessions to terminate. When they do not, the password change is security theater.
|
|
855
|
+
|
|
856
|
+
**The fix:**
|
|
857
|
+
```python
|
|
858
|
+
@app.route('/api/change-password', methods=['POST'])
|
|
859
|
+
@login_required
|
|
860
|
+
def change_password():
|
|
861
|
+
current_user.password = hash_password(request.json['new_password'])
|
|
862
|
+
current_user.token_version += 1 # Increment token version
|
|
863
|
+
current_user.save()
|
|
864
|
+
|
|
865
|
+
# Invalidate all refresh tokens
|
|
866
|
+
RefreshToken.query.filter_by(user_id=current_user.id).delete()
|
|
867
|
+
|
|
868
|
+
# Invalidate all sessions
|
|
869
|
+
Session.query.filter_by(user_id=current_user.id).delete()
|
|
870
|
+
db.session.commit()
|
|
871
|
+
|
|
872
|
+
# Issue a new token for the current session only
|
|
873
|
+
new_token = generate_token(current_user)
|
|
874
|
+
return jsonify({"status": "password changed", "token": new_token})
|
|
875
|
+
|
|
876
|
+
# In token verification, check token_version
|
|
877
|
+
def verify_token(token):
|
|
878
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
|
879
|
+
user = User.query.get(payload['sub'])
|
|
880
|
+
if user.token_version != payload.get('tv'):
|
|
881
|
+
raise InvalidTokenError("Token revoked")
|
|
882
|
+
return payload
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
**Detection rule:**
|
|
886
|
+
Flag password-change handlers that do not invalidate sessions, refresh tokens, or increment a token version counter. Flag JWT verification logic that does not check a per-user version or revocation list.
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
### AP-17: Role-Based Access Without Resource-Level Checks
|
|
891
|
+
|
|
892
|
+
**Also known as:** Coarse-grained authorization, vertical-only access control, role-is-enough fallacy
|
|
893
|
+
**Frequency:** Common
|
|
894
|
+
**Severity:** High
|
|
895
|
+
**Detection difficulty:** Hard
|
|
896
|
+
|
|
897
|
+
**What it looks like:**
|
|
898
|
+
```python
|
|
899
|
+
@app.route('/api/projects/<project_id>/settings', methods=['PUT'])
|
|
900
|
+
@login_required
|
|
901
|
+
@require_role('manager') # Checks that user is a manager...
|
|
902
|
+
def update_project_settings(project_id):
|
|
903
|
+
# ...but not that they manage THIS project
|
|
904
|
+
project = Project.query.get(project_id)
|
|
905
|
+
project.settings = request.json
|
|
906
|
+
project.save()
|
|
907
|
+
return jsonify(project.settings)
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
**Why developers do it:**
|
|
911
|
+
Role-based access control (RBAC) is the most commonly taught authorization model. Frameworks make role checks easy. The mental model is "managers can manage things" rather than "this manager can manage these specific things." Resource-level (or attribute-based) access control requires more data in the authorization decision.
|
|
912
|
+
|
|
913
|
+
**What goes wrong:**
|
|
914
|
+
A manager in Team A can modify settings for Team B's projects. This is horizontal privilege escalation within a role. The fix for AP-05 (IDOR) addresses unauthenticated/unauthorized access, but this pattern is subtler: the user has the right role but not the right scope. This is especially dangerous in multi-tenant SaaS applications where a role in Tenant A should grant zero access to Tenant B.
|
|
915
|
+
|
|
916
|
+
**The fix:**
|
|
917
|
+
```python
|
|
918
|
+
@app.route('/api/projects/<project_id>/settings', methods=['PUT'])
|
|
919
|
+
@login_required
|
|
920
|
+
@require_role('manager')
|
|
921
|
+
def update_project_settings(project_id):
|
|
922
|
+
# Check resource-level access, not just role
|
|
923
|
+
project = Project.query.get(project_id)
|
|
924
|
+
if not project:
|
|
925
|
+
abort(404)
|
|
926
|
+
if current_user.id not in project.manager_ids:
|
|
927
|
+
abort(403)
|
|
928
|
+
project.settings = request.json
|
|
929
|
+
project.save()
|
|
930
|
+
return jsonify(project.settings)
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
For complex scenarios, use a policy engine (OPA, Casbin, Cedar) that evaluates `(subject, action, resource)` tuples rather than `(subject, role)` alone.
|
|
934
|
+
|
|
935
|
+
**Detection rule:**
|
|
936
|
+
Flag endpoints that call role-check middleware but do not subsequently verify the relationship between the authenticated user and the specific resource being accessed. Flag authorization logic that does not reference the resource ID.
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
940
|
+
### AP-18: Trusting Client-Provided User Identity
|
|
941
|
+
|
|
942
|
+
**Also known as:** User ID in request body, role from the client, self-declared identity
|
|
943
|
+
**Frequency:** Common
|
|
944
|
+
**Severity:** Critical
|
|
945
|
+
**Detection difficulty:** Moderate
|
|
946
|
+
|
|
947
|
+
**What it looks like:**
|
|
948
|
+
```python
|
|
949
|
+
@app.route('/api/orders', methods=['POST'])
|
|
950
|
+
def create_order():
|
|
951
|
+
# User ID comes from the request body, not from the authenticated session
|
|
952
|
+
order = Order(
|
|
953
|
+
user_id=request.json['user_id'], # Attacker sets any user_id
|
|
954
|
+
items=request.json['items']
|
|
955
|
+
)
|
|
956
|
+
db.session.add(order)
|
|
957
|
+
db.session.commit()
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
```javascript
|
|
961
|
+
// Client sends role claim in request
|
|
962
|
+
fetch('/api/admin/action', {
|
|
963
|
+
method: 'POST',
|
|
964
|
+
body: JSON.stringify({ userId: 42, role: 'admin', action: 'delete_user' })
|
|
965
|
+
});
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
**Why developers do it:**
|
|
969
|
+
During development, it is convenient to pass the user ID from the client. The developer intends to "fix it later" when auth is fully implemented. In some architectures, the user context is passed between frontend and backend without server-side verification.
|
|
970
|
+
|
|
971
|
+
**What goes wrong:**
|
|
972
|
+
Any user can impersonate any other user by changing the user_id field. CWE-602 and CWE-603 explicitly categorize this as a vulnerability. The OWASP Authorization Cheat Sheet states: "Access control checks must be performed server-side. Authorization and authentication controls must be re-enforced on the server-side."
|
|
973
|
+
|
|
974
|
+
**The fix:**
|
|
975
|
+
```python
|
|
976
|
+
@app.route('/api/orders', methods=['POST'])
|
|
977
|
+
@login_required
|
|
978
|
+
def create_order():
|
|
979
|
+
order = Order(
|
|
980
|
+
user_id=current_user.id, # Always from the server session, never from the request
|
|
981
|
+
items=request.json['items']
|
|
982
|
+
)
|
|
983
|
+
db.session.add(order)
|
|
984
|
+
db.session.commit()
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
**Detection rule:**
|
|
988
|
+
Flag request body fields named `user_id`, `userId`, `user`, `role`, or `permissions` that are used in authorization decisions. Flag any endpoint where `request.json['user_id']` or `request.body.userId` is assigned to a model without comparison to the session user.
|
|
989
|
+
|
|
990
|
+
---
|
|
991
|
+
|
|
992
|
+
### AP-19: Missing Rate Limiting on Auth Endpoints
|
|
993
|
+
|
|
994
|
+
**Also known as:** Unrestricted authentication, no throttling, open credential stuffing target
|
|
995
|
+
**Frequency:** Very Common
|
|
996
|
+
**Severity:** High
|
|
997
|
+
**Detection difficulty:** Easy
|
|
998
|
+
|
|
999
|
+
**What it looks like:**
|
|
1000
|
+
```python
|
|
1001
|
+
# All auth endpoints wide open
|
|
1002
|
+
@app.route('/api/login', methods=['POST'])
|
|
1003
|
+
def login():
|
|
1004
|
+
return attempt_login(request.json)
|
|
1005
|
+
|
|
1006
|
+
@app.route('/api/register', methods=['POST'])
|
|
1007
|
+
def register():
|
|
1008
|
+
return create_account(request.json)
|
|
1009
|
+
|
|
1010
|
+
@app.route('/api/forgot-password', methods=['POST'])
|
|
1011
|
+
def forgot_password():
|
|
1012
|
+
return send_reset_email(request.json['email'])
|
|
1013
|
+
|
|
1014
|
+
@app.route('/api/verify-otp', methods=['POST'])
|
|
1015
|
+
def verify_otp():
|
|
1016
|
+
return check_otp(request.json)
|
|
1017
|
+
# Attacker can brute-force 6-digit OTP (1 million combinations) in minutes
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
**Why developers do it:**
|
|
1021
|
+
Rate limiting is infrastructure, not application logic. Developers expect it to be handled by the API gateway, load balancer, or CDN -- but nobody configures it. Application-level rate limiting requires Redis or similar state storage, which adds deployment complexity.
|
|
1022
|
+
|
|
1023
|
+
**What goes wrong:**
|
|
1024
|
+
OWASP API Security Top 10 (API2:2023 Broken Authentication) explicitly calls out that "anti-brute force mechanisms should be implemented to mitigate credential stuffing, dictionary attacks, and brute force attacks on authentication endpoints." Without rate limiting:
|
|
1025
|
+
- Login endpoints enable credential stuffing at scale.
|
|
1026
|
+
- Password reset endpoints enable token enumeration.
|
|
1027
|
+
- OTP endpoints enable brute-force bypass of MFA (a 6-digit OTP has only 1 million combinations).
|
|
1028
|
+
- Registration endpoints enable mass account creation for spam.
|
|
1029
|
+
|
|
1030
|
+
**The fix:**
|
|
1031
|
+
```python
|
|
1032
|
+
from flask_limiter import Limiter
|
|
1033
|
+
|
|
1034
|
+
limiter = Limiter(app, key_func=get_remote_address, storage_uri="redis://localhost:6379")
|
|
1035
|
+
|
|
1036
|
+
@app.route('/api/login', methods=['POST'])
|
|
1037
|
+
@limiter.limit("5/minute", key_func=lambda: request.json.get('email', get_remote_address()))
|
|
1038
|
+
def login():
|
|
1039
|
+
return attempt_login(request.json)
|
|
1040
|
+
|
|
1041
|
+
@app.route('/api/forgot-password', methods=['POST'])
|
|
1042
|
+
@limiter.limit("3/hour")
|
|
1043
|
+
def forgot_password():
|
|
1044
|
+
return send_reset_email(request.json['email'])
|
|
1045
|
+
|
|
1046
|
+
@app.route('/api/verify-otp', methods=['POST'])
|
|
1047
|
+
@limiter.limit("5/minute")
|
|
1048
|
+
def verify_otp():
|
|
1049
|
+
return check_otp(request.json)
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
Rate limit by both IP and account identifier. Use exponential backoff on repeated failures.
|
|
1053
|
+
|
|
1054
|
+
**Detection rule:**
|
|
1055
|
+
Flag authentication endpoints (login, register, forgot-password, verify-otp, verify-mfa) that lack rate-limiting middleware. Flag absence of rate-limiting infrastructure (Redis, Memcached) in deployment configs when cookie/session auth is used.
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
### AP-20: Logging Sensitive Authentication Data
|
|
1060
|
+
|
|
1061
|
+
**Also known as:** Credentials in logs, PII leakage to observability, debug logging in production
|
|
1062
|
+
**Frequency:** Common
|
|
1063
|
+
**Severity:** High
|
|
1064
|
+
**Detection difficulty:** Moderate
|
|
1065
|
+
|
|
1066
|
+
**What it looks like:**
|
|
1067
|
+
```python
|
|
1068
|
+
@app.route('/login', methods=['POST'])
|
|
1069
|
+
def login():
|
|
1070
|
+
logger.info(f"Login attempt: email={request.form['email']}, password={request.form['password']}")
|
|
1071
|
+
# Password is now in plaintext in log files, shipped to Splunk/ELK/CloudWatch
|
|
1072
|
+
# Accessible to ops team, log aggregation service, anyone with log access
|
|
1073
|
+
|
|
1074
|
+
user = authenticate(request.form['email'], request.form['password'])
|
|
1075
|
+
if user:
|
|
1076
|
+
token = generate_token(user)
|
|
1077
|
+
logger.info(f"Login success: user={user.id}, token={token}")
|
|
1078
|
+
# Token is now in logs -- anyone with log access can impersonate this user
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
**Why developers do it:**
|
|
1082
|
+
Debug logging during development includes everything for easy troubleshooting. When code moves to production, nobody audits log statements. Structured logging libraries that auto-serialize request objects can inadvertently capture sensitive fields.
|
|
1083
|
+
|
|
1084
|
+
**What goes wrong:**
|
|
1085
|
+
- **Twitter (2018):** Disclosed that a bug caused user passwords to be written to an internal log in plaintext. All 330 million users were asked to change their passwords.
|
|
1086
|
+
- **Facebook (2019):** Discovered that hundreds of millions of passwords were stored in readable format in internal data storage systems. Engineers had logged passwords during authentication.
|
|
1087
|
+
|
|
1088
|
+
OWASP's logging guidelines explicitly prohibit logging: authentication passwords, session identification values, access tokens, database connection strings, encryption keys, and bank account or payment card holder data.
|
|
1089
|
+
|
|
1090
|
+
**The fix:**
|
|
1091
|
+
```python
|
|
1092
|
+
import re
|
|
1093
|
+
|
|
1094
|
+
SENSITIVE_FIELDS = {'password', 'token', 'secret', 'api_key', 'authorization', 'cookie', 'ssn', 'credit_card'}
|
|
1095
|
+
|
|
1096
|
+
class SanitizingFormatter(logging.Formatter):
|
|
1097
|
+
def format(self, record):
|
|
1098
|
+
msg = super().format(record)
|
|
1099
|
+
for field in SENSITIVE_FIELDS:
|
|
1100
|
+
msg = re.sub(
|
|
1101
|
+
rf'({field}\s*[=:]\s*)([^\s,;]+)',
|
|
1102
|
+
rf'\1[REDACTED]',
|
|
1103
|
+
msg,
|
|
1104
|
+
flags=re.IGNORECASE
|
|
1105
|
+
)
|
|
1106
|
+
return msg
|
|
1107
|
+
|
|
1108
|
+
# Application logging
|
|
1109
|
+
logger.info(f"Login attempt: email={request.form['email']}")
|
|
1110
|
+
# Never log: passwords, tokens, session IDs, API keys
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
**Detection rule:**
|
|
1114
|
+
Grep log statements for variables named `password`, `token`, `secret`, `api_key`, `authorization`, `session_id`, `credit_card`. Flag `logger.*` calls that interpolate request body or header fields without a sanitization filter. Flag structured loggers configured to auto-serialize entire request objects.
|
|
1115
|
+
|
|
1116
|
+
---
|
|
1117
|
+
|
|
1118
|
+
## Root Cause Analysis
|
|
1119
|
+
|
|
1120
|
+
| Root Cause | Anti-Patterns | Frequency |
|
|
1121
|
+
|---|---|---|
|
|
1122
|
+
| **Treating auth as a one-time setup** | AP-05, AP-06, AP-17 | Very High |
|
|
1123
|
+
| **Prioritizing speed over security** | AP-02, AP-07, AP-12, AP-18 | Very High |
|
|
1124
|
+
| **Misunderstanding crypto primitives** | AP-01, AP-02, AP-04 | High |
|
|
1125
|
+
| **Following outdated tutorials** | AP-03, AP-04, AP-10 | High |
|
|
1126
|
+
| **Missing lifecycle management** | AP-13, AP-15, AP-16 | High |
|
|
1127
|
+
| **Assuming the client is trusted** | AP-06, AP-09, AP-18 | High |
|
|
1128
|
+
| **Security features deferred to "later"** | AP-07, AP-12, AP-14, AP-19 | Very High |
|
|
1129
|
+
| **Confusing authentication with authorization** | AP-05, AP-06, AP-17 | High |
|
|
1130
|
+
| **Insufficient logging hygiene** | AP-20 | Medium |
|
|
1131
|
+
| **Framework defaults accepted uncritically** | AP-08, AP-11 | Medium |
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
## Self-Check Questions
|
|
1136
|
+
|
|
1137
|
+
Use these questions during code review or architecture review to surface auth anti-patterns:
|
|
1138
|
+
|
|
1139
|
+
1. **Password storage:** Are passwords hashed with bcrypt, scrypt, or Argon2id? Can you confirm MD5/SHA1/SHA256 are NOT used for password hashing?
|
|
1140
|
+
|
|
1141
|
+
2. **Token storage:** Where are authentication tokens stored on the client? If localStorage, what is the XSS mitigation strategy? Are cookies marked HttpOnly, Secure, and SameSite?
|
|
1142
|
+
|
|
1143
|
+
3. **JWT validation:** Does the server explicitly whitelist allowed JWT algorithms? Is `"none"` rejected? Are `exp`, `aud`, and `iss` claims validated?
|
|
1144
|
+
|
|
1145
|
+
4. **Resource-level authorization:** For every endpoint that takes a resource ID, does the query/check verify the authenticated user has access to that specific resource (not just the right role)?
|
|
1146
|
+
|
|
1147
|
+
5. **Credential rotation:** What happens to existing sessions and tokens when a user changes their password? Are they invalidated?
|
|
1148
|
+
|
|
1149
|
+
6. **Secret management:** Are there any API keys, database passwords, or signing secrets in the source code or configuration files checked into version control? Is a pre-commit scanner configured?
|
|
1150
|
+
|
|
1151
|
+
7. **Rate limiting:** What happens if an attacker sends 10,000 login requests in one minute? What about 10,000 password reset requests? What about 1 million OTP verification attempts?
|
|
1152
|
+
|
|
1153
|
+
8. **Session lifecycle:** Is the session ID regenerated after login? Does the session expire? Can a user see and terminate other active sessions?
|
|
1154
|
+
|
|
1155
|
+
9. **OAuth flow:** Are redirect URIs validated against an exact-match whitelist? Is the `state` parameter generated and validated? Is PKCE used for public clients?
|
|
1156
|
+
|
|
1157
|
+
10. **Reset tokens:** Are password reset tokens cryptographically random, single-use, time-limited, and stored hashed? Can a used token be replayed?
|
|
1158
|
+
|
|
1159
|
+
11. **MFA coverage:** Which sensitive operations require re-authentication or step-up MFA? Is MFA enforced for password change, email change, API key creation, and fund transfers?
|
|
1160
|
+
|
|
1161
|
+
12. **Logging:** Do log statements capture passwords, tokens, session IDs, or API keys? Are request objects auto-serialized without field-level filtering?
|
|
1162
|
+
|
|
1163
|
+
13. **Authorization enforcement:** For every client-side route guard or UI visibility check, is there a corresponding server-side authorization check on the API endpoint?
|
|
1164
|
+
|
|
1165
|
+
14. **Token lifetime:** What is the access token lifetime? Is it under 1 hour? Are refresh tokens stored server-side and revocable?
|
|
1166
|
+
|
|
1167
|
+
---
|
|
1168
|
+
|
|
1169
|
+
## Code Smell Quick Reference
|
|
1170
|
+
|
|
1171
|
+
| Code Smell | Likely Anti-Pattern | Severity |
|
|
1172
|
+
|---|---|---|
|
|
1173
|
+
| `hashlib.md5(password)` or `hashlib.sha1(password)` | AP-02: Weak password hashing | Critical |
|
|
1174
|
+
| `localStorage.setItem('token', ...)` | AP-03: XSS-accessible tokens | High |
|
|
1175
|
+
| `jwt.decode(token, verify=False)` | AP-04: Unverified JWT | Critical |
|
|
1176
|
+
| `jwt.decode(token, algorithms=None)` | AP-04: Algorithm confusion risk | Critical |
|
|
1177
|
+
| `db.query(Model).get(request_param_id)` without user filter | AP-05: IDOR | Critical |
|
|
1178
|
+
| API route with `@login_required` but no role/resource check | AP-06/AP-17: Missing authorization | High |
|
|
1179
|
+
| String literal matching `AKIA*`, `sk_live_*`, `ghp_*` | AP-07: Hardcoded credentials | Critical |
|
|
1180
|
+
| `session['user'] = ...` without `session.regenerate()` | AP-08: Session fixation | High |
|
|
1181
|
+
| POST handler with cookie auth and no CSRF token check | AP-09: Missing CSRF protection | High |
|
|
1182
|
+
| `redirect_uri` accepted from query params without whitelist validation | AP-10: OAuth open redirect | Critical |
|
|
1183
|
+
| Auto-increment integer IDs in API URLs | AP-11: Enumerable identifiers | High |
|
|
1184
|
+
| Login handler without attempt counting or rate-limit decorator | AP-12/AP-19: No brute-force protection | High |
|
|
1185
|
+
| `hashlib.md5(email)` as reset token | AP-13: Predictable reset token | Critical |
|
|
1186
|
+
| Password change endpoint without session invalidation | AP-16: Zombie tokens | High |
|
|
1187
|
+
| `request.json['user_id']` used in data model assignment | AP-18: Trusting client identity | Critical |
|
|
1188
|
+
| `logger.info(f"...password={password}...")` | AP-20: Credentials in logs | High |
|
|
1189
|
+
| `timedelta(days=30)` in JWT `exp` claim | AP-15: Excessive token lifetime | High |
|
|
1190
|
+
| `jwt.encode({...}, SECRET)` without `exp` claim | AP-15: Non-expiring token | High |
|
|
1191
|
+
| Sensitive endpoint without `@require_mfa` or re-auth check | AP-14: Missing step-up auth | High |
|
|
1192
|
+
|
|
1193
|
+
---
|
|
1194
|
+
|
|
1195
|
+
*Researched: 2026-03-08 | Sources: OWASP Top 10 (2021, 2025), OWASP API Security Top 10, OWASP Cheat Sheet Series, Auth0 JWT vulnerability disclosure (2015), GitGuardian State of Secrets Sprawl (2023, 2024), Have I Been Pwned (Adobe, LinkedIn, RockYou), Portswigger Web Security Academy, PentesterLab JWT Guide, Salt Labs (Booking.com), CVE-2023-6927 (Keycloak), CVE-2026-28415 (Gradio), CVE-2026-28268 (Vikunja), CVE-2019-3778 (Spring Security OAuth), HackerOne reports (Weblate, Acronis, Mavenlink), CWE-602, CWE-603, Schneier on Security, Cybernews (RockYou2024), BleepingComputer, Wiz AI secrets research*
|