-
Notifications
You must be signed in to change notification settings - Fork 336
Implement SCA Reachability runtime detection: report vulnerable classes and callsites via telemetry #11352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jandro996
wants to merge
69
commits into
master
Choose a base branch
from
alejandro.gonzalez/sca-reachability
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Implement SCA Reachability runtime detection: report vulnerable classes and callsites via telemetry #11352
Changes from all commits
Commits
Show all changes
69 commits
Select commit
Hold shift + click to select a range
28f12e0
Implement SCA Reachability: detect vulnerable library classes at runtime
jandro996 e607887
Commit sca_cves.json as versioned resource; update generateScaCvesJso…
jandro996 fb9d011
Fix Path B classpath scan for Java 9+: fall back to java.class.path
jandro996 62b290d
Add Java 9+ test for Path B classpath fallback; make method package-p…
jandro996 93d58f2
Implement method-level symbol detection with ASM bytecode injection
jandro996 a5ccd80
Retransform classes for method-level detection: already-loaded and ve…
jandro996 f8f9d02
Fix: remove incorrect dedup from injectCallbacks; update invariants
jandro996 82ea806
pr-review: fix null guard, encapsulate periodicWorkCallback, update J…
jandro996 39eef44
Fix two Codex review issues: java.nio in premain and transitive JAR r…
jandro996 dc8ffd3
Refactor: extract CLASS_LEVEL_SYMBOL constant and reportClassLevelHit…
jandro996 849f376
Move CLASS_LEVEL_SYMBOL to ScaReachabilityHit; fix misleading comment
jandro996 3ea0e05
Move java.nio comment to usage site; add tests for transitive JAR fal…
jandro996 525a81c
Remove dead visitCode() override and redundant CLASS_LEVEL_SYMBOL alias
jandro996 7f5e116
Capture callsite for method-level hits (mirrors Python tracer)
jandro996 e0c7fee
Move callsite detection from bootstrap to ScaReachabilitySystem handler
jandro996 a79907d
Use AbstractStackWalker.isNotDatadogTraceStackElement for callsite fi…
jandro996 77ba03f
Add tests for ScaReachabilitySystem.findCallsite(); document fallback…
jandro996 7c69a89
Update ScaReachabilityHit Javadoc to reflect dual callsite/symbol sem…
jandro996 19a5813
Move findCallsite() after start() — helpers after main public method
jandro996 3b76b33
Use ConcurrentHashMap.newKeySet() instead of verbose newSetFromMap idiom
jandro996 6008ac9
Lazy entryHasMethodLevelSymbol check — avoid stream alloc on normal path
jandro996 17146c0
Remove Path B from startup scan — JDK symbols are false positive indi…
jandro996 750b3c3
Remove dead processPathB() — never called after Path B removal
jandro996 fdb74e4
Fix dedup key to include class name for method-level hits
jandro996 b90b654
Implement stateful RFC heartbeat model for SCA telemetry
jandro996 fdcb421
Add smoke test for SCA Reachability telemetry (APPSEC-62260)
jandro996 579bbd0
Add method-level symbols for jackson-databind deserialization CVEs
jandro996 9c89c1b
Add method-level symbols for xstream, log4j, snakeyaml, jackson-mappe…
jandro996 00185f1
Fix SCA smoke test, RFC compliance and add heartbeat flow tests
jandro996 b3582e8
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 a12d3f9
fix(smoke): add braces to if statement to satisfy CodeNarc IfStatemen…
jandro996 32fb0d1
fix(spotbugs): make periodicWorkCallback private, expose via getter
jandro996 54332fa
refactor: replace Map<?,?> casts with typed Moshi DTOs in ScaCveDatabase
jandro996 d469821
cleanup: remove stale Path A/B terminology after Path B was removed
jandro996 ab5850b
chore: remove .claude-invariants.md from tracking, add to .gitignore
jandro996 2fbf3ed
fix(forbiddenapis): replace String#split() with pre-compiled Pattern.…
jandro996 bbd32bb
fix: remove class-level symbols from all xstream entries
jandro996 d312b48
feat: emit metadata:[] for all deps in DependencyPeriodicAction when …
jandro996 d6a419e
fix(spotbugs): replace URL collections with URI to avoid DNS lookups …
jandro996 310aa66
chore: remove dead ScaReachabilityCollector, fix stale Javadoc, drop …
jandro996 e0a2067
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 b0421ab
refactor: unify dep reporting into ScaReachabilityPeriodicAction when…
jandro996 364fc13
fix(techdebt): static imports, remove inline java.util refs, replace …
jandro996 b529d09
fix: restore ScaReachabilityPeriodicActionTest; fix raw type Class[0]…
jandro996 90be403
fix(techdebt): move pendingRetransformNames to field section; json-es…
jandro996 5dd23b1
fix(techdebt): extract depKey helper; delete empty test stub ScaReach…
jandro996 164a838
fix(thread-safety): use AtomicReference.compareAndSet for first-hit-w…
jandro996 2e1e8ee
fix: remove stale .claude-invariants.md reference from ScaReachabilit…
jandro996 058a344
fix: wrap onMethodHit in try/catch to prevent exception propagation t…
jandro996 b425f33
fix: use knownDeps to enrich CVE snapshots with source/hash from prio…
jandro996 6925358
fix: emit CVE data immediately in Step 3, use knownDeps only for sour…
jandro996 24c53f0
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 0da4c37
fix(sca): force snakeyaml class load in smoke test via PostConstruct
jandro996 354cd35
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 5a1400a
ci: retrigger CI
jandro996 50df877
refactor(sca): remove dead markPending, inline scheduleRetransformByN…
jandro996 58df388
fix(sca): register only ScaReachabilityPeriodicAction when SCA enable…
jandro996 c327257
revert: restore pre-existing em dashes in GatewayBridge, ObjectIntros…
jandro996 ba0aa7a
fix: hoist dotClassName conversion outside inner loop in processClass
jandro996 7b449cf
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 05e2537
fix(sca): deduplicate index entries per class when entry has multiple…
jandro996 42039ba
fix(sca): include version in hit dedup keys to isolate multi-version …
jandro996 7eb20bf
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 1d29fb6
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 b00c531
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 53c36f5
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 ca35526
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 834c65c
Merge branch 'master' into alejandro.gonzalez/sca-reachability
jandro996 54a7fa7
fix(sca): skip intermediate library frames in callsite detection
jandro996 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
buildSrc/src/main/kotlin/datadog/gradle/sca/GhsaEnrichmentParser.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package datadog.gradle.sca | ||
|
|
||
| import com.fasterxml.jackson.databind.JsonNode | ||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
|
|
||
| /** | ||
| * Parses GHSA enrichment JSON files from the sca-reachability-database into the internal | ||
| * sca_cves.json format consumed by SCA Reachability at runtime. | ||
| * | ||
| * Key transformations: | ||
| * - Filters entries to JVM language only | ||
| * - Expands multi-package GHSA entries into N records (one per Maven artifact), because | ||
| * each artifact may have different version ranges for the same set of class symbols | ||
| * - Converts class FQNs to JVM internal format (slashes) so the ClassFileTransformer | ||
| * can do O(1) map lookups without per-class string conversion | ||
| * - Sets method=null for all symbols — field exists for forward compatibility when the | ||
| * database adds method-level symbols in the future (see APPSEC-62260) | ||
| */ | ||
| object GhsaEnrichmentParser { | ||
|
|
||
| private val mapper = ObjectMapper() | ||
|
|
||
| /** | ||
| * Parses a single GHSA enrichment file. | ||
| * | ||
| * @param ghsaId the GHSA identifier (e.g. "GHSA-645p-88qh-w398"), used as vuln_id | ||
| * @param jsonContent the raw JSON content of the enrichment file | ||
| * @return list of sca_cves.json entry maps, one per affected Maven artifact | ||
| */ | ||
| fun parse(ghsaId: String, jsonContent: String): List<Map<String, Any?>> { | ||
| val root = mapper.readTree(jsonContent) | ||
| require(root.isArray) { "GHSA enrichment file $ghsaId must be a JSON array, got ${root.nodeType}" } | ||
|
|
||
| val entries = mutableListOf<Map<String, Any?>>() | ||
|
|
||
| for (entry in root) { | ||
| if (entry.path("language").asText() != "jvm") continue | ||
|
|
||
| val symbols = extractSymbols(entry) | ||
| if (symbols.isEmpty()) continue | ||
|
|
||
| for (pkg in entry.path("package")) { | ||
| if (pkg.path("ecosystem").asText() != "maven") continue | ||
| val artifact = pkg.path("name").asText().takeIf { it.isNotEmpty() } ?: continue | ||
| val versionRanges = pkg.path("version_range").map { it.asText() } | ||
|
|
||
| entries += mapOf( | ||
| "vuln_id" to ghsaId, | ||
| "artifact" to artifact, | ||
| "version_ranges" to versionRanges, | ||
| "symbols" to symbols, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| return entries | ||
| } | ||
|
|
||
| private fun extractSymbols(entry: JsonNode): List<Map<String, Any?>> { | ||
| val symbols = mutableListOf<Map<String, Any?>>() | ||
| val imports = entry.path("ecosystem_specific").path("imports") | ||
| if (imports.isMissingNode || !imports.isArray) return symbols | ||
|
|
||
| for (importGroup in imports) { | ||
| for (symbol in importGroup.path("symbols")) { | ||
| if (symbol.path("type").asText() != "class") continue | ||
| val pkg = symbol.path("value").asText().takeIf { it.isNotEmpty() } ?: continue | ||
| val name = symbol.path("name").asText().takeIf { it.isNotEmpty() } ?: continue | ||
|
|
||
| // JVM internal format (slashes) — avoids per-class conversion in the | ||
| // ClassFileTransformer hot path at runtime | ||
| val internalName = "$pkg.$name".replace('.', '/') | ||
| symbols += mapOf("class" to internalName, "method" to null) | ||
| } | ||
| } | ||
|
|
||
| return symbols | ||
| } | ||
| } | ||
117 changes: 117 additions & 0 deletions
117
buildSrc/src/test/kotlin/datadog/gradle/sca/GhsaEnrichmentParserTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| package datadog.gradle.sca | ||
|
|
||
| import org.assertj.core.api.Assertions.assertThat | ||
| import org.assertj.core.api.Assertions.assertThatThrownBy | ||
| import org.junit.jupiter.api.Test | ||
|
|
||
| class GhsaEnrichmentParserTest { | ||
|
|
||
| private fun fixture(name: String): String = | ||
| GhsaEnrichmentParserTest::class.java | ||
| .getResourceAsStream("/sca/fixtures/$name")!! | ||
| .bufferedReader() | ||
| .readText() | ||
|
|
||
| @Test | ||
| fun `single package entry produces one record`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-single-package", fixture("GHSA-single-package.json")) | ||
|
|
||
| assertThat(entries).hasSize(1) | ||
| val entry = entries[0] | ||
| assertThat(entry["vuln_id"]).isEqualTo("GHSA-single-package") | ||
| assertThat(entry["artifact"]).isEqualTo("com.fasterxml.jackson.core:jackson-databind") | ||
| assertThat(entry["version_ranges"]).isEqualTo(listOf("< 2.6.7.3", ">= 2.7.0, < 2.7.9.5")) | ||
| } | ||
|
|
||
| @Test | ||
| fun `class names are converted to JVM internal format with slashes`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-single-package", fixture("GHSA-single-package.json")) | ||
|
|
||
| @Suppress("UNCHECKED_CAST") | ||
| val symbols = entries[0]["symbols"] as List<Map<String, Any?>> | ||
| assertThat(symbols).hasSize(2) | ||
| assertThat(symbols.map { it["class"] }).containsExactly( | ||
| "com/fasterxml/jackson/databind/ObjectMapper", | ||
| "com/fasterxml/jackson/databind/ObjectReader", | ||
| ) | ||
| } | ||
|
|
||
| @Test | ||
| fun `method field is always null for class-level symbols`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-single-package", fixture("GHSA-single-package.json")) | ||
|
|
||
| @Suppress("UNCHECKED_CAST") | ||
| val symbols = entries[0]["symbols"] as List<Map<String, Any?>> | ||
| assertThat(symbols).allSatisfy { symbol -> | ||
| assertThat(symbol["method"]).isNull() | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| fun `multi-package entry expands to one record per artifact`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-multi-package", fixture("GHSA-multi-package.json")) | ||
|
|
||
| assertThat(entries).hasSize(2) | ||
| assertThat(entries.map { it["artifact"] }).containsExactlyInAnyOrder( | ||
| "org.springframework.boot:spring-boot-starter-web", | ||
| "org.springframework:spring-webmvc", | ||
| ) | ||
| } | ||
|
|
||
| @Test | ||
| fun `multi-package entries each have their own version ranges`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-multi-package", fixture("GHSA-multi-package.json")) | ||
|
|
||
| val webEntry = entries.first { it["artifact"] == "org.springframework.boot:spring-boot-starter-web" } | ||
| assertThat(webEntry["version_ranges"]).isEqualTo(listOf("< 2.5.12", ">= 2.6.0, < 2.6.6")) | ||
|
|
||
| val mvcEntry = entries.first { it["artifact"] == "org.springframework:spring-webmvc" } | ||
| assertThat(mvcEntry["version_ranges"]).isEqualTo(listOf(">= 5.3.0, < 5.3.18", "< 5.2.20.RELEASE")) | ||
| } | ||
|
|
||
| @Test | ||
| fun `multi-package entries share the same symbols`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-multi-package", fixture("GHSA-multi-package.json")) | ||
|
|
||
| @Suppress("UNCHECKED_CAST") | ||
| val symbols0 = entries[0]["symbols"] as List<Map<String, Any?>> | ||
| @Suppress("UNCHECKED_CAST") | ||
| val symbols1 = entries[1]["symbols"] as List<Map<String, Any?>> | ||
| assertThat(symbols0.map { it["class"] }).containsExactlyInAnyOrder( | ||
| "org/springframework/stereotype/Controller", | ||
| "org/springframework/web/bind/annotation/RestController", | ||
| ) | ||
| assertThat(symbols0.map { it["class"] }).isEqualTo(symbols1.map { it["class"] }) | ||
| } | ||
|
|
||
| @Test | ||
| fun `non-jvm language entries are ignored`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-mixed-languages", fixture("GHSA-mixed-languages.json")) | ||
|
|
||
| assertThat(entries).hasSize(1) | ||
| assertThat(entries[0]["artifact"]).isEqualTo("com.thoughtworks.xstream:xstream") | ||
| } | ||
|
|
||
| @Test | ||
| fun `entries with no symbols produce no output`() { | ||
| val entries = GhsaEnrichmentParser.parse("GHSA-empty-symbols", fixture("GHSA-empty-symbols.json")) | ||
|
|
||
| assertThat(entries).isEmpty() | ||
| } | ||
|
|
||
| @Test | ||
| fun `ghsa id is used as vuln_id without modification`() { | ||
| val ghsaId = "GHSA-645p-88qh-w398" | ||
| val entries = GhsaEnrichmentParser.parse(ghsaId, fixture("GHSA-single-package.json")) | ||
|
|
||
| assertThat(entries[0]["vuln_id"]).isEqualTo(ghsaId) | ||
| } | ||
|
|
||
| @Test | ||
| fun `non-json-array input throws IllegalArgumentException`() { | ||
| assertThatThrownBy { | ||
| GhsaEnrichmentParser.parse("GHSA-bad", """{"language": "jvm"}""") | ||
| }.isInstanceOf(IllegalArgumentException::class.java) | ||
| .hasMessageContaining("must be a JSON array") | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
buildSrc/src/test/resources/sca/fixtures/GHSA-empty-symbols.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| [ | ||
| { | ||
| "language": "jvm", | ||
| "package": [ | ||
| { | ||
| "ecosystem": "maven", | ||
| "name": "org.example:some-lib", | ||
| "version_range": ["< 1.0.0"] | ||
| } | ||
| ], | ||
| "ecosystem_specific": { | ||
| "imports": [] | ||
| } | ||
| } | ||
| ] |
40 changes: 40 additions & 0 deletions
40
buildSrc/src/test/resources/sca/fixtures/GHSA-mixed-languages.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| [ | ||
| { | ||
| "language": "python", | ||
| "package": [ | ||
| { | ||
| "ecosystem": "pypi", | ||
| "name": "requests", | ||
| "version_range": ["< 2.28.0"] | ||
| } | ||
| ], | ||
| "ecosystem_specific": { | ||
| "imports": [ | ||
| { | ||
| "symbols": [ | ||
| {"type": "function", "value": "requests", "name": "get"} | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| }, | ||
| { | ||
| "language": "jvm", | ||
| "package": [ | ||
| { | ||
| "ecosystem": "maven", | ||
| "name": "com.thoughtworks.xstream:xstream", | ||
| "version_range": ["< 1.4.16"] | ||
| } | ||
| ], | ||
| "ecosystem_specific": { | ||
| "imports": [ | ||
| { | ||
| "symbols": [ | ||
| {"type": "class", "value": "com.thoughtworks.xstream", "name": "XStream"} | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ] |
41 changes: 41 additions & 0 deletions
41
buildSrc/src/test/resources/sca/fixtures/GHSA-multi-package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| [ | ||
| { | ||
| "language": "jvm", | ||
| "package": [ | ||
| { | ||
| "ecosystem": "maven", | ||
| "name": "org.springframework.boot:spring-boot-starter-web", | ||
| "version_range": [ | ||
| "< 2.5.12", | ||
| ">= 2.6.0, < 2.6.6" | ||
| ] | ||
| }, | ||
| { | ||
| "ecosystem": "maven", | ||
| "name": "org.springframework:spring-webmvc", | ||
| "version_range": [ | ||
| ">= 5.3.0, < 5.3.18", | ||
| "< 5.2.20.RELEASE" | ||
| ] | ||
| } | ||
| ], | ||
| "ecosystem_specific": { | ||
| "imports": [ | ||
| { | ||
| "symbols": [ | ||
| { | ||
| "type": "class", | ||
| "value": "org.springframework.stereotype", | ||
| "name": "Controller" | ||
| }, | ||
| { | ||
| "type": "class", | ||
| "value": "org.springframework.web.bind.annotation", | ||
| "name": "RestController" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ] |
33 changes: 33 additions & 0 deletions
33
buildSrc/src/test/resources/sca/fixtures/GHSA-single-package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| [ | ||
| { | ||
| "language": "jvm", | ||
| "package": [ | ||
| { | ||
| "ecosystem": "maven", | ||
| "name": "com.fasterxml.jackson.core:jackson-databind", | ||
| "version_range": [ | ||
| "< 2.6.7.3", | ||
| ">= 2.7.0, < 2.7.9.5" | ||
| ] | ||
| } | ||
| ], | ||
| "ecosystem_specific": { | ||
| "imports": [ | ||
| { | ||
| "symbols": [ | ||
| { | ||
| "type": "class", | ||
| "value": "com.fasterxml.jackson.databind", | ||
| "name": "ObjectMapper" | ||
| }, | ||
| { | ||
| "type": "class", | ||
| "value": "com.fasterxml.jackson.databind", | ||
| "name": "ObjectReader" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.